Letting Workers Go Because of Covid-19

During the on-going coronavirus pandemic, many retailers have furloughed or laid off workers. While this is never an easy process, good communication may help make it relatively less emotional and painful for everyone involved.

If your company has decided to let some workers go permanently, it is not alone. Many well-known omnichannel retail businesses have already announced significant layoffs.

On June 3, 2020, The Economist reported that “American retailers have laid off or furloughed one-fifth of their workers.”

Macy’s, for example, furloughed nearly all of its workers in March 2020 as shelter-in-place orders were announced across the country. Then on June 25, 2020, the company said that 3,900 of its employees would be let go permanently.

Sur la Table, a Seattle-based kitchen supply retailer, confirmed last week that it laid off 20 percent of its corporate staff (27 employees) as a result of the pandemic. While this is a much smaller number than Macy’s, you can bet it was still a trying experience for each person who lost a job and for everyone who remained.

Now add to this layoffs at many other retailers, such as Nordstrom, J. C. Penney, J.Crew, and Neiman Marcus, to name just a few.

Layoffs Hurt

“Letting people go is an emotional event — not just for those being laid off but for those who remain,” wrote business advisor and entrepreneur Stever Robbins in a 2009 Harvard Business Review article.

Many articles and papers echo Robbins’ point about laying off employees and doing it well. The literature recognizes that the layoff process is difficult under the best of circumstances, and the Covid-19-induced recession is not the best of circumstances.

Retail business leaders who have decided that a layoff is necessary should next recognize that communication is compassion.

All involved will want to understand how they are personally impacted (i.e., do they still have a job?), why the layoff was necessary, and how the layoff will improve the business’s performance and help to ensure its survival.

“Managers often think they shouldn’t let employees know when things are going poorly,” Robbins wrote. “They don’t want their workers to become discouraged. But people aren’t stupid; they know when things aren’t going well. Even if top managers spin the circumstances positively, the message comes across through unclear goals, a decrease in resources committed to on-going projects, and other subtle clues. Discussing and acknowledging the company’s position is the first step to keeping people involved — and committed to solving problems they understand.”

<img aria-describedby="caption-attachment-353265" class="wp-image-353265 size-large" src="https://566763.smushcdn.com/754212/wp-content/uploads/2020/07/070220-layoff-meeting-570×380.jpg?lossy=1&strip=1&webp=1" alt="Business leaders should communicate directly with employees, explaining who will be laid off, why, and how it will help the company. Photo: Dylan Gillis.” width=”570″ height=”380″ srcset=”https://566763.smushcdn.com/754212/wp-content/uploads/2020/07/070220-layoff-meeting-570×380.jpg?lossy=1&strip=1&webp=1 570w, https://566763.smushcdn.com/754212/wp-content/uploads/2020/07/070220-layoff-meeting-300×200.jpg?lossy=1&strip=1&webp=1 300w, https://566763.smushcdn.com/754212/wp-content/uploads/2020/07/070220-layoff-meeting-768×512.jpg?lossy=1&strip=1&webp=1 768w, https://566763.smushcdn.com/754212/wp-content/uploads/2020/07/070220-layoff-meeting-150×100.jpg?lossy=1&strip=1&webp=1 150w, https://566763.smushcdn.com/754212/wp-content/uploads/2020/07/070220-layoff-meeting-500×334.jpg?lossy=1&strip=1&webp=1 500w, https://566763.smushcdn.com/754212/wp-content/uploads/2020/07/070220-layoff-meeting.jpg?lossy=1&strip=1&webp=1 1000w” sizes=”(max-width: 570px) 100vw, 570px”>

Business leaders should communicate directly with employees, explaining who will be laid off, why, and how it will help the company. Photo: Dylan Gillis.

When employees are let go, company leaders should meet with groups and individuals to communicate what is happening, when it is happening, and why.

Prepare, but be Quick

While it is important not to delay communicating a layoff, it is also necessary to think about what to communicate.

“If you decide layoffs are necessary or others have made that decision for you, then make sure you’re prepared before you reach out to the affected employees,” wrote Rebecca Knight in an April 2020 article also from the Harvard Business Review.

Effectively, this means understanding if the retailer can help employees with the transition to a new job. If not, can the retailer offer any sort of exit package, and at the very least, can the retailer point laid-off workers to government programs like unemployment?

Business leaders should also be prepared to answer questions like, “What happens to my 401(k)?” and “Will I get paid for my vacation time?”

Ultimately, a company should do its best to help laid-off workers understand what is happening to them and help them cope. How these things are communicated should be consistent and compassionate. They should also be done as soon as possible.

Companies should also communicate with employees who survived the layoff.

These folks “also need reassurance about their own future — and an understanding of the strategic goals behind the cuts,” said Robbins.

“Reassurance” is an important part of what needs to be communicated. Surviving employees should not be worried that they are next. They want to know that the appropriate cuts are complete, and the company (or, more specifically, their job) will last.

Work Load

After retail business leaders have communicated with laid-off and remaining workers, it is time for a second round of conversations to explain how workloads will be shifted.

For surviving employees, this is the next logical question. Once they know that they will not lose their job, and once they understand the strategy behind the layoffs, those employees will want to know how their projects and responsibilities will change.

SEO How-to, Part 6: Optimizing On-page Elements

Keyword research can help improve your organic search performance. Search engines attempt to sync the words and intent of consumers’ queries with web pages. Ecommerce merchants should therefore align their pages using the right keywords to convey the proper intent.

This post is the sixth installment in my “SEO How-to” series, following:

Body copy is important. But the title tag is still the most critical SEO element on a page. To be sure, it’s not enough to simply optimize the title tag without the other items. All of those elements — the meta description, heading tags, keywords in the URL, and alternative attributes on image tags — should sing the same keyword theme.

SEO Elements

It’s helpful to know what each of these content optimization elements looks like in the code of a web page. Consider the screenshot, below.

  • Blue highlights the title tag (“Purchase Quilting Fabric…”).
  • Yellow highlights the meta description (“Shop thousands of bolts…”).
  • Grey highlights the meta keywords (“fabric by the yard, cheap fabric by the yard…”).
  • Green highlights the H1 heading (“Fabric by the Yard”).
  • Purple highlights the body copy (“With thousands of bolts…”).
Important SEO elements in code include the title tag ("Purchase Quilting Fabric…”), the meta description ("Shop thousands of bolts…”), meta keywords ("fabric by the yard, cheap fabric by the yard…”), an H1 heading ("Fabric by the Yard”), and body copy ("With thousands of bolts...").

Essential SEO elements in code include the title tag (“Purchase Quilting Fabric…”), the meta description (“Shop thousands of bolts…”), meta keywords (“fabric by the yard, cheap fabric by the yard…”), an H1 heading (“Fabric by the Yard”), and body copy (“With thousands of bolts…”).

And here’s how that page looks on the frontend. I’ve highlighted the title tag in blue, the H1 heading in green, and the body copy in purple.

Key SEO elements that are visual on the frontend include the title tag (blue), the H1 heading (green), and the body copy (purple).

Key SEO elements that are visual on the frontend include the title tag (blue), the H1 heading (green), and the body copy (purple).

Each content element comes with its own guidelines for optimization. Most content management systems allow you to modify these elements, though they may call them by different names.

Title Tags

Title tags remain the most important on-page factor.

Google limits title tags in search results to 60 characters. Thus try to restrict your title tags to 60 characters and place the most relevant keywords at the front.  Don’t panic if you go over by a character or two. Product names and blog post titles tend to be longer.

You won’t be penalized for extended title tags unless you stuff them full of unnatural keywords. The portion after 60 characters simply won’t show.

The title tag often appears as the blue text link in a search result listing, as shown below. Search engines use the title tag — or some version of it — to introduce consumers to your content. Thus the tag should appeal to searchers as well as search engines.

The title tag often appears as the blue text link in your search result listing. This example listing also includes the meta description.

The title tag often appears as the blue text link in your search result listing. This example listing also includes the meta description.

Meta Descriptions

Meta descriptions impact a searcher’s click decision but not rankings. Search engines may choose to use meta descriptions as the black descriptive text below the blue link in a search result.

Limit meta descriptions to 160 characters — enough to populate two lines in a desktop search results page. Some informational searches can merit a third or even fourth line, especially when Google can provide the answer directly in the search results page as a rich snippet. In these cases, the search engine may pull text from the body copy or a field of structured data.

Always provide a unique meta description that describes the page and ends in a call to action. Be sure to use the targeted keyword theme. Search engines place in bold type the words that match the searcher’s query.

Meta Keywords

Leave the meta keywords field blank. Do not use it. No major U.S. search engine has used meta keywords in its ranking algorithm since 2009. (The Chinese search engine, Baidu, does use meta keywords, however.) Inserting meta keywords gives your competitors an easy way to identify what you’re trying to rank for.

Keyword URLs

Place keywords in URLs if your content management system or ecommerce platform allows it. But do so wisely. Set the URL once — when the page goes live — incorporating the most relevant keyword for that page. Do not change it again unless the content on the page changes so radically that you’re forced to.

For example, do not change the keywords in the URL every time you optimize the page. URLs are like street addresses, and search engines are like the post office. Every time you change your street address, some of your mail — your search performance — goes missing. It may find you again eventually if you have 301 redirects in place. But then again, it may not. Don’t risk your natural search performance by changing your URLs unnecessarily.

Heading Tags

Headings (such as H1, H2, H3) help readers and search engines alike. At times, it’s hard for the two functions to coexist.

For optimal search optimization, a heading tag should use the same keyword theme as the other elements (title tags, meta descriptions, body copy), which can result in a longer phrase. However, editors and marketers tend to prefer short headings for reading and comprehension. (For example, “Heading Tags,” above, is an H3 header. Practical Ecommerce prefers shorter headings, even though longer ones, such as “How to Optimize Heading Tags,” might perform better for SEO.)

Try to explain in a heading the core relevance of the page or section of a page. Usually all it takes is a noun with a modifier — such as “women’s shoes” instead of just “women’s” or “shoes” — to help search engine algorithms understand what shoppers know by reading.

Advances in HTML specifications allow more than one H1 heading on a page. But don’t abuse it. Search engines likely would consider, for example, 10 H1 headings with trophy keywords as over-optimization. Lesser headings —  H2, H3 — communicate relevance almost as well.

Body Content

Text tends to be much shorter on an ecommerce site than, say, an informational site such as a blog, wiki, or similar. For ecommerce, try to include a line of text on the home page, a couple of lines on each category page, and a description on product pages. Content such as articles or FAQ pages should be as long as needed.

On each page, use the keyword at least once, as close to the start as you can without appearing forced. Include the keyword again, or another contextually relevant keyword, if the content is long enough and if it naturally flows in the text.

Your priority in content optimization should be well-written copy that shoppers find interesting or useful. No one wants to read “SEO copy” — content that has been over-optimized with strings of keywords and text that doesn’t communicate anything useful. It’s painful and turns off shoppers.

Well-optimized content uses the real-world language of shoppers — not marketing-speak or industry jargon.

Try to insert in the body copy a couple of links that your shoppers would find relevant. Such links have two important SEO benefits: They contribute to the keyword theme on the page where the link occurs, and they pass link authority and keyword context to the page being linked to. Make sure that your platform supports updating the link or 301 redirecting pages. This will avoid broken links when your URLs change.

Alternative Attributes

Also called “alt tags,” alternative attributes to image tags are more important for accessibility than for SEO. Screen readers speak the text in the alt attributes to help visually-impaired shoppers navigate a site.

However, alt attributes can add a small keyword relevance boost, and they are especially helpful in optimizing image search. Keep them short and descriptive. For product images, use the name of the item. If the name is not descriptive, include a keyword or two.

For images that include words, place those words into the alt attribute. Don’t insert alt text in images that are for decoration (such as lifestyle pictures of smiling people) or formatting (spacers, dividing lines, bullets).

Do not stuff alt attributes with keywords. There’s no SEO benefit, and it’s a terrible user experience. If you wouldn’t want to listen to a screen reader speaking them, take the words out.

How To Test Your React Apps With The React Testing Library

How To Test Your React Apps With The React Testing Library

How To Test Your React Apps With The React Testing Library

Chidi Orji

2020-07-03T12:30:00+00:00 2020-07-03T16:34:19+00:00

Today, we’ll briefly discuss why it’s important to write automated tests for any software project, and shed light on some of the common types of automated testing. We’ll build a to-do list app by following the Test-Driven Development (TDD) approach. I’ll show you how to write both unit and functional tests, and in the process, explain what code mocks are by mocking a few libraries. I’ll be using a combination of RTL and Jest — both of which come pre-installed in any new project created with Create-React-App (CRA).

To follow along, you need to know how to set up and navigate a new React project and how to work with the yarn package manager (or npm). Familiarities with Axios and React-Router are also required.

Best Practices With React

React is a fantastic JavaScript library for building rich user interfaces. It provides a great component abstraction for organizing your interfaces into well-functioning code, and there’s just about anything you can use it for. Read more articles on React →

Why You Should Test Your Code

Before shipping your software to end-users, you first have to confirm that it is working as expected. In other words, the app should satisfy its project specifications.

Just as it is important to test our project as a whole before shipping it to end-users, it’s also essential to keep testing our code during the lifetime of a project. This is necessary for a number of reasons. We may make updates to our application or refactor some parts of our code. A third-party library may undergo a breaking change. Even the browser that is running our web application may undergo breaking changes. In some cases, something stops working for no apparent reason — things could go wrong unexpectedly. Thus, it is necessary to test our code regularly for the lifetime of a project.

Broadly speaking, there are manual and automated software tests. In a manual test, a real user performs some action on our application to verify that they work correctly. This kind of test is less reliable when repeated several times because it’s easy for the tester to miss some details between test runs.

In an automated test, however, a test script is executed by a machine. With a test script, we can be sure that whatever details we set in the script will remain unchanged on every test run.

This kind of test gives us the benefits of being predictable and fast, such that we can quickly find and fix bugs in our code.

Having seen the necessity of testing our code, the next logical question is, what sort of automated tests should we write for our code? Let’s quickly go over a few of them.

Types Of Automated Testing

There are many different types of automated software testing. Some of the most common ones are unit tests, integration tests, functional tests, end-to-end tests, acceptance tests, performance tests, and smoke tests.

  1. Unit test
    In this kind of test, the goal is to verify that each unit of our application, considered in isolation, is working correctly. An example would be testing that a particular function returns an expected value, give some known inputs. We’ll see several examples in this article.
  2. Smoke test
    This kind of test is done to check that the system is up and running. For example, in a React app, we could just render our main app component and call it a day. If it renders correctly we can be fairly certain that our app would render on the browser.
  3. Integration test
    This sort of test is carried out to verify that two or more modules can work well together. For example, you might run a test to verify that your server and database are actually communicating correctly.
  4. Functional test
    A functional test exists to verify that the system meets its functional specification. We’ll see an example later.
  5. End-to-end test
    This kind of test involves testing the application the same way it would be used in the real world. You can use a tool like cypress for E2E tests.
  6. Acceptance test
    This is usually done by the business owner to verify that the system meets specifications.
  7. Performance test
    This sort of testing is carried out to see how the system performs under significant load. In frontend development, this is usually about how fast the app loads on the browser.

There’s more here if you’re interested.

Why Use React Testing Library?

When it comes to testing React applications, there are a few testing options available, of which the most common ones I know of are Enzyme and React Testing Library (RTL).

RTL is a subset of the @testing-library family of packages. Its philosophy is very simple. Your users don’t care whether you use redux or context for state management. They care less about the simplicity of hooks nor the distinction between class and functional components. They just want your app to work in a certain way. It is, therefore, no surprise that the testing library’s primary guiding principle is

“The more your tests resemble the way your software is used, the more confidence they can give you.”

So, whatever you do, have the end-user in mind and test your app just as they would use it.

Choosing RTL gives you a number of advantages. First, it’s much easier to get started with it. Every new React project bootstrapped with CRA comes with RTL and Jest configured. The React docs also recommend it as the testing library of choice. Lastly, the guiding principle makes a lot of sense — functionality over implementation details.

With that out of the way, let’s get started with building a to-do list app, following the TDD approach.

Project Setup

Open a terminal and copy and run the below command.

# start new react project and start the server
npx create-react-app start-rtl && cd start-rtl && yarn start

This should create a new React project and start the server on http://localhost:3000. With the project running, open a separate terminal, run yarn test and then press a. This runs all tests in the project in watch mode. Running the test in watch mode means that the test will automatically re-run when it detects a change in either the test file or the file that is being tested. On the test terminal, you should see something like the picture below:

Initial test passing
Initial test passing. (Large preview)

You should see a lot of greens, which indicates that the test we’re running passed in flying colors.

As I mentioned earlier, CRA sets up RTL and Jest for every new React project. It also includes a sample test. This sample test is what we just executed.

When you run the yarn test command, react-scripts calls upon Jest to execute the test. Jest is a JavaScript testing framework that’s used in running tests. You won’t find it listed in package.json but you can do a search inside yarn.lock to find it. You can also see it in node_modules/.

Jest is incredible in the range of functionality that it provides. It provides tools for assertions, mocking, spying, etc. I strongly encourage you to take at least a quick tour of the documentation. There’s a lot to learn there that I cannot scratch in this short piece. We’ll be using Jest a lot in the coming sections.

Open package.json let’s see what we have there. The section of interest is dependencies.

 "dependencies": { "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", ... },

We have the following packages installed specifically for testing purpose:

  1. @testing-library/jest-dom: provides custom DOM element matchers for Jest.
  2. @testing-library/react: provides the APIs for testing React apps.
  3. @testing-library/user-event: provides advanced simulation of browser interactions.

Open up App.test.js let’s take a look at its content.

import React from 'react';
import { render } from '@testing-library/react';
import App from './App'; test('renders learn react link', () => { const { getByText } = render(); const linkElement = getByText(/learn react/i); expect(linkElement).toBeInTheDocument();
});

The render method of RTL renders the <App /> component and returns an object which is de-structured for the getByText query. This query finds elements in the DOM by their display text. Queries are the tools for finding elements in the DOM. The complete list of queries can be found here. All of the queries from the testing library are exported by RTL, in addition to the render, cleanup, and act methods. You can read more about these in the API section.

The text is matched with the regular expression /learn react/i. The i flag makes the regular expression case-insensitive. We expect to find the text Learn React in the document.

All of this mimics the behavior a user would experience in the browser when interacting with our app.

Let’s start making the changes required by our app. Open App.js and replace the content with the below code.

import React from "react";
import "./App.css";
function App() { return ( <div className="App"> <header className="App-header"> <h2>Getting started with React testing library</h2> </header> </div> );
}
export default App;

If you still have the test running, you should see the test fail. Perhaps you can guess why that is the case, but we’ll return to it a bit later. Right now I want to refactor the test block.

Replace the test block in src/App.test.js with the code below:

# use describe, it pattern
describe("<App />", () => { it("Renders <App /> component correctly", () => { const { getByText } = render(<App />); expect(getByText(/Getting started with React testing library/i)).toBeInTheDocument(); });
});

This refactor makes no material difference to how our test will run. I prefer the describe and it pattern as it allows me structure my test file into logical blocks of related tests. The test should re-run and this time it will pass. In case you haven’t guessed it, the fix for the failing test was to replace the learn react text with Getting started with React testing library.

In case you don’t have time to write your own styles you can just copy the one below into App.css.

.App { min-height: 100vh; text-align: center;
}
.App-header { height: 10vh; display: flex; background-color: #282c34; flex-direction: column; align-items: center; justify-content: center; font-size: calc(10px + 2vmin); color: white;
}
.App-body { width: 60%; margin: 20px auto;
}
ul { padding: 0; display: flex; list-style-type: decimal; flex-direction: column;
}
li { font-size: large; text-align: left; padding: 0.5rem 0;
}
li a { text-transform: capitalize; text-decoration: none;
}
.todo-title { text-transform: capitalize;
}
.completed { color: green;
}
.not-completed { color: red;
}

You should already see the page title move up after adding this CSS.

I consider this a good point for me to commit my changes and push to Github. The corresponding branch is 01-setup.

Let’s continue with our project setup. We know we’re going to need some navigation in our app so we need React-Router. We’ll also be making API calls with Axios. Let’s install both.

# install react-router-dom and axios
yarn add react-router-dom axios

Most React apps you’ll build will have to maintain state. There’s a lot of libraries available for managing state. But for this tutorial, I’ll be using React’s context API and the useContext hook. So let’s set up our app’s context.

Create a new file src/AppContext.js and enter the below content.

import React from "react";
export const AppContext = React.createContext({}); export const AppProvider = ({ children }) => { const reducer = (state, action) => { switch (action.type) { case "LOAD_TODOLIST": return { ...state, todoList: action.todoList }; case "LOAD_SINGLE_TODO": return { ...state, activeToDoItem: action.todo }; default: return state; } }; const [appData, appDispatch] = React.useReducer(reducer, { todoList: [], activeToDoItem: { id: 0 }, }); return ( <AppContext.Provider value={{ appData, appDispatch }}> {children} </AppContext.Provider> );
};

Here we create a new context with React.createContext({}), for which the initial value is an empty object. We then define an AppProvider component that accepts children component. It then wraps those children in AppContext.Provider, thus making the { appData, appDispatch } object available to all children anywhere in the render tree.

Our reducer function defines two action types.

  1. LOAD_TODOLIST which is used to update the todoList array.
  2. LOAD_SINGLE_TODO which is used to update activeToDoItem.

appData and appDispatch are both returned from the useReducer hook. appData gives us access to the values in the state while appDispatch gives us a function which we can use to update the app’s state.

Now open index.js, import the AppProvider component and wrap the <App /> component with <AppProvider />. Your final code should look like what I have below.

import { AppProvider } from "./AppContext"; ReactDOM.render( <React.StrictMode> <AppProvider> <App /> </AppProvider> </React.StrictMode>, document.getElementById("root")
);

Wrapping <App /> inside <AppProvider /> makes AppContext available to every child component in our app.

Remember that with RTL, the aim is to test our app the same way a real user would interact with it. This implies that we also want our tests to interact with our app state. For that reason, we also need to make our <AppProvider /> available to our components during tests. Let’s see how to make that happen.

The render method provided by RTL is sufficient for simple components that don’t need to maintain state or use navigation. But most apps require at least one of both. For this reason, it provides a wrapper option. With this wrapper, we can wrap the UI rendered by the test renderer with any component we like, thus creating a custom render. Let’s create one for our tests.

Create a new file src/custom-render.js and paste the following code.

import React from "react";
import { render } from "@testing-library/react";
import { MemoryRouter } from "react-router-dom"; import { AppProvider } from "./AppContext"; const Wrapper = ({ children }) => { return ( <AppProvider> <MemoryRouter>{children}</MemoryRouter> </AppProvider> );
}; const customRender = (ui, options) => render(ui, { wrapper: Wrapper, ...options }); // re-export everything
export * from "@testing-library/react"; // override render method
export { customRender as render };

Here we define a <Wrapper /> component that accepts some children component. It then wraps those children inside <AppProvider /> and <MemoryRouter />. MemoryRouter is

A <Router> that keeps the history of your “URL” in memory (does not read or write to the address bar). Useful in tests and non-browser environments like React Native.

We then create our render function, providing it the Wrapper we just defined through its wrapper option. The effect of this is that any component we pass to the render function is rendered inside <Wrapper />, thus having access to navigation and our app’s state.

The next step is to export everything from @testing-library/react. Lastly, we export our custom render function as render, thus overriding the default render.

Note that even if you were using Redux for state management the same pattern still applies.

Let’s now make sure our new render function works. Import it into src/App.test.js and use it to render the <App /> component.

Open App.test.js and replace the import line. This

import { render } from '@testing-library/react';

should become

import { render } from './custom-render';

Does the test still pass? Good job.

There’s one small change I want to make before wrapping up this section. It gets tiring very quickly to have to write const { getByText } and other queries every time. So, I’m going to be using the screen object from the DOM testing library henceforth.

Import the screen object from our custom render file and replace the describe block with the code below.

import { render, screen } from "./custom-render"; describe("<App />", () => { it("Renders <App /> component correctly", () => { render(<App />); expect( screen.getByText(/Getting started with React testing library/i) ).toBeInTheDocument(); });
});

We’re now accessing the getByText query from the screen object. Does your test still pass? I’m sure it does. Let’s continue.

If your tests don’t pass you may want to compare your code with mine. The corresponding branch at this point is 02-setup-store-and-render.

Testing And Building The To-Do List Index Page

In this section, we’ll pull to-do items from http://jsonplaceholder.typicode.com/. Our component specification is very simple. When a user visits our app homepage,

  1. show a loading indicator that says Fetching todos while waiting for the response from the API;
  2. display the title of 15 to-do items on the screen once the API call returns (the API call returns 200). Also, each item title should be a link that will lead to the to-do details page.

Following a test-driven approach, we’ll write our test before implementing the component logic. Before doing that we’ll need to have the component in question. So go ahead and create a file src/TodoList.js and enter the following content:

import React from "react";
import "./App.css";
export const TodoList = () => { return ( <div> </div> );
};

Since we know the component specification we can test it in isolation before incorporating it into our main app. I believe it’s up to the developer at this point to decide how they want to handle this. One reason you might want to test a component in isolation is so that you don’t accidentally break any existing test and then having to fight fires in two locations. With that out of the way let’s now write the test.

Create a new file src/TodoList.test.js and enter the below code:

import React from "react";
import axios from "axios";
import { render, screen, waitForElementToBeRemoved } from "./custom-render";
import { TodoList } from "./TodoList";
import { todos } from "./makeTodos"; describe("<App />", () => { it("Renders <TodoList /> component", async () => { render(<TodoList />); await waitForElementToBeRemoved(() => screen.getByText(/Fetching todos/i)); expect(axios.get).toHaveBeenCalledTimes(1); todos.slice(0, 15).forEach((td) => { expect(screen.getByText(td.title)).toBeInTheDocument(); }); });
});

Inside our test block, we render the <TodoList /> component and use the waitForElementToBeRemoved function to wait for the Fetching todos text to disappear from the screen. Once this happens we know that our API call has returned. We also check that an Axios get call was fired once. Finally, we check that each to-do title is displayed on the screen. Note that the it block receives an async function. This is necessary for us to be able to use await inside the function.

Each to-do item returned by the API has the following structure.

{ id: 0, userId: 0, title: 'Some title', completed: true,
}

We want to return an array of these when we

import { todos } from "./makeTodos"

The only condition is that each id should be unique.

Create a new file src/makeTodos.js and enter the below content. This is the source of todos we’ll use in our tests.

const makeTodos = (n) => { // returns n number of todo items // default is 15 const num = n || 15; const todos = []; for (let i = 0; i < num; i++) { todos.push({ id: i, userId: i, title: `Todo item ${i}`, completed: [true, false][Math.floor(Math.random() * 2)], }); } return todos;
}; export const todos = makeTodos(200);

This function simply generates a list of n to-do items. The completed line is set by randomly choosing between true and false.

Unit tests are supposed to be fast. They should run within a few seconds. Fail fast! This is one of the reasons why letting our tests make actual API calls is impractical. To avoid this we mock such unpredictable API calls. Mocking simply means replacing a function with a fake version, thus allowing us to customize the behavior. In our case, we want to mock the get method of Axios to return whatever we want it to. Jest already provides mocking functionality out of the box.

Let’s now mock Axios so it returns this list of to-dos when we make the API call in our test. Create a file src/__mocks__/axios.js and enter the below content:

import { todos } from "../makeTodos"; export default { get: jest.fn().mockImplementation((url) => { switch (url) { case "https://jsonplaceholder.typicode.com/todos": return Promise.resolve({ data: todos }); default: throw new Error(`UNMATCHED URL: ${url}`); } }),
};

When the test starts, Jest automatically finds this mocks folder and instead of using the actual Axios from node_modules/ in our tests, it uses this one. At this point, we’re only mocking the get method using Jest’s mockImplementation method. Similarly, we can mock other Axios methods like post, patch, interceptors, defaults etc. Right now they’re all undefined and any attempt to access, axios.post for example, would result in an error.

Note that we can customize what to return based on the URL the Axios call receives. Also, Axios calls return a promise which resolves to the actual data we want, so we return a promise with the data we want.

At this point, we have one passing test and one failing test. Let’s implement the component logic.

Open src/TodoList.js let’s build out the implementation piece by piece. Start by replacing the code inside with this one below.

import React from "react";
import axios from "axios";
import { Link } from "react-router-dom";
import "./App.css";
import { AppContext } from "./AppContext"; export const TodoList = () => { const [loading, setLoading] = React.useState(true); const { appData, appDispatch } = React.useContext(AppContext); React.useEffect(() => { axios.get("https://jsonplaceholder.typicode.com/todos").then((resp) => { const { data } = resp; appDispatch({ type: "LOAD_TODOLIST", todoList: data }); setLoading(false); }); }, [appDispatch, setLoading]); return ( <div> // next code block goes here </div> );
};

We import AppContext and de-structure appData and appDispatch from the return value of React.useContext. We then make the API call inside a useEffect block. Once the API call returns, we set the to-do list in state by firing the LOAD_TODOLIST action. Finally, we set the loading state to false to reveal our to-dos.

Now enter the final piece of code.

{loading ? ( <p>Fetching todos</p>
) : ( <ul> {appData.todoList.slice(0, 15).map((item) => { const { id, title } = item; return ( <li key={id}> <Link to={`/item/${id}`} data-testid={id}> {title} </Link> </li> ); })} </ul>
)}

We slice appData.todoList to get the first 15 items. We then map over those and render each one in a <Link /> tag so we can click on it and see the details. Note the data-testid attribute on each Link. This should be a unique ID that will aid us in finding individual DOM elements. In a case where we have similar text on the screen, we should never have the same ID for any two elements. We’ll see how to use this a bit later.

My tests now pass. Does yours pass? Great.

Let’s now incorporate this component into our render tree. Open up App.js let’s do that.

First things. Add some imports.

import { BrowserRouter, Route } from "react-router-dom";
import { TodoList } from "./TodoList";

We need BrowserRouter for navigation and Route for rendering each component in each navigation location.

Now add the below code after the <header /> element.

<div className="App-body"> <BrowserRouter> <Route exact path="/" component={TodoList} /> </BrowserRouter>
</div>

This is simply telling the browser to render the <TodoList /> component when we’re on the root location, /. Once this is done, our tests still pass but you should see some error messages on your console telling you about some act something. You should also see that the <TodoList /> component seems to be the culprit here.

Terminal showing act warnings
Terminal showing act warnings. (Large preview)

Since we’re sure that our TodoList component by itself is okay, we have to look at the App component, inside of which is rendered the <TodoList /> component.

This warning may seem complex at first but it is telling us that something is happening in our component that we’re not accounting for in our test. The fix is to wait for the loading indicator to be removed from the screen before we proceed.

Open up App.test.js and update the code to look like so:

import React from "react";
import { render, screen, waitForElementToBeRemoved } from "./custom-render";
import App from "./App";
describe("<App />", () => { it("Renders <App /> component correctly", async () => { render(<App />); expect( screen.getByText(/Getting started with React testing library/i) ).toBeInTheDocument(); await waitForElementToBeRemoved(() => screen.getByText(/Fetching todos/i)); });
});

We’ve made two changes. First, we changed the function in the it block to an async function. This is a necessary step to allow us to use await in the function body. Secondly, we wait for the Fetching todos text to be removed from the screen. And voila!. The warning is gone. Phew! I strongly advise that you bookmark this post by Kent Dodds for more on this act warning. You’re gonna need it.

Now open the page in your browser and you should see the list of to-dos. You can click on an item if you like, but it won’t show you anything because our router doesn’t yet recognize that URL.

For comparison, the branch of my repo at this point is 03-todolist.

Let’s now add the to-do details page.

Testing And Building The Single To-Do Page

To display a single to-do item we’ll follow a similar approach. The component specification is simple. When a user navigates to a to-do page:

  1. display a loading indicator that says Fetching todo item id where id represents the to-do’s id, while the API call to https://jsonplaceholder.typicode.com/todos/item_id runs.
  2. When the API call returns, show the following information:
    • Todo item title
    • Added by: userId
    • This item has been completed if the to-do has been completed or
    • This item is yet to be completed if the to-do has not been completed.

Let’s start with the component. Create a file src/TodoItem.js and add the following content.

import React from "react";
import { useParams } from "react-router-dom"; import "./App.css"; export const TodoItem = () => { const { id } = useParams() return ( <div className="single-todo-item"> </div> );
};

The only thing new to us in this file is the const { id } = useParams() line. This is a hook from react-router-dom that lets us read URL parameters. This id is going to be used in fetching a to-do item from the API.

This situation is a bit different because we’re going to be reading the id from the location URL. We know that when a user clicks a to-do link, the id will show up in the URL which we can then grab using the useParams() hook. But here we’re testing the component in isolation which means that there’s nothing to click, even if we wanted to. To get around this we’ll have to mock react-router-dom, but only some parts of it. Yes. It’s possible to mock only what we need to. Let’s see how it’s done.

Create a new mock file src/__mocks__ /react-router-dom.js. Now paste in the following code:

module.exports = { ...jest.requireActual("react-router-dom"), useParams: jest.fn(),
};

By now you should have noticed that when mocking a module we have to use the exact module name as the mock file name.

Here, we use the module.exports syntax because react-router-dom has mostly named exports. (I haven’t come across any default export since I’ve been working with it. If there are any, kindly share with me in the comments). This is unlike Axios where everything is bundled as methods in one default export.

We first spread the actual react-router-dom, then replace the useParams hook with a Jest function. Since this function is a Jest function, we can modify it anytime we want. Keep in mind that we’re only mocking the part we need to because if we mock everything, we’ll lose the implementation of MemoryHistory which is used in our render function.

Let’s start testing!

Now create src/TodoItem.test.js and enter the below content:

import React from "react";
import axios from "axios";
import { render, screen, waitForElementToBeRemoved } from "./custom-render";
import { useParams, MemoryRouter } from "react-router-dom";
import { TodoItem } from "./TodoItem"; describe("<TodoItem />", () => { it("can tell mocked from unmocked functions", () => { expect(jest.isMockFunction(useParams)).toBe(true); expect(jest.isMockFunction(MemoryRouter)).toBe(false); });
});

Just like before, we have all our imports. The describe block then follows. Our first case is only there as a demonstration that we’re only mocking what we need to. Jest’s isMockFunction can tell whether a function is mocked or not. Both expectations pass, confirming the fact that we have a mock where we want it.

Add the below test case for when a to-do item has been completed.

 it("Renders <TodoItem /> correctly for a completed item", async () => { useParams.mockReturnValue({ id: 1 }); render(<TodoItem />); await waitForElementToBeRemoved(() => screen.getByText(/Fetching todo item 1/i) ); expect(axios.get).toHaveBeenCalledTimes(1); expect(screen.getByText(/todo item 1/)).toBeInTheDocument(); expect(screen.getByText(/Added by: 1/)).toBeInTheDocument(); expect( screen.getByText(/This item has been completed/) ).toBeInTheDocument(); });

The very first thing we do is to mock the return value of useParams. We want it to return an object with an id property, having a value of 1. When this is parsed in the component, we end up with the following URL https://jsonplaceholder.typicode.com/todos/1. Keep in mind that we have to add a case for this URL in our Axios mock or it will throw an error. We will do that in just a moment.

We now know for sure that calling useParams() will return the object { id: 1 } which makes this test case predictable.

As with previous tests, we wait for the loading indicator, Fetching todo item 1 to be removed from the screen before making our expectations. We expect to see the to-do title, the id of the user who added it, and a message indicating the status.

Open src/__mocks__/axios.js and add the following case to the switch block.

 case "https://jsonplaceholder.typicode.com/todos/1": return Promise.resolve({ data: { id: 1, title: "todo item 1", userId: 1, completed: true }, });

When this URL is matched, a promise with a completed to-do is returned. Of course, this test case fails since we’re yet to implement the component logic. Go ahead and add a test case for when the to-do item has not been completed.

 it("Renders <TodoItem /> correctly for an uncompleted item", async () => { useParams.mockReturnValue({ id: 2 }); render(<TodoItem />); await waitForElementToBeRemoved(() => screen.getByText(/Fetching todo item 2/i) ); expect(axios.get).toHaveBeenCalledTimes(2); expect(screen.getByText(/todo item 2/)).toBeInTheDocument(); expect(screen.getByText(/Added by: 2/)).toBeInTheDocument(); expect( screen.getByText(/This item is yet to be completed/) ).toBeInTheDocument(); });

This is the same as the previous case. The only difference is the ID of the to-do, the userId, and the completion status. When we enter the component, we’ll need to make an API call to the URL https://jsonplaceholder.typicode.com/todos/2. Go ahead and add a matching case statement to the switch block of our Axios mock.

case "https://jsonplaceholder.typicode.com/todos/2": return Promise.resolve({ data: { id: 2, title: "todo item 2", userId: 2, completed: false }, });

When the URL is matched, a promise with an uncompleted to-do is returned.

Both test cases are failing. Now let’s add the component implementation to make them pass.

Open src/TodoItem.js and update the code to the following:

import React from "react";
import axios from "axios";
import { useParams } from "react-router-dom";
import "./App.css";
import { AppContext } from "./AppContext"; export const TodoItem = () => { const { id } = useParams(); const [loading, setLoading] = React.useState(true); const { appData: { activeToDoItem }, appDispatch, } = React.useContext(AppContext); const { title, completed, userId } = activeToDoItem; React.useEffect(() => { axios .get(`https://jsonplaceholder.typicode.com/todos/${id}`) .then((resp) => { const { data } = resp; appDispatch({ type: "LOAD_SINGLE_TODO", todo: data }); setLoading(false); }); }, [id, appDispatch]); return ( <div className="single-todo-item"> // next code block goes here. </div> );
};

As with the <TodoList /> component, we import AppContext. We read activeTodoItem from it, then we read the to-do title, userId, and completion status. After that we make the API call inside a useEffect block. When the API call returns we set the to-do in state by firing the LOAD_SINGLE_TODO action. Finally, we set our loading state to false to reveal the to-do details.

Let’s add the final piece of code inside the return div:

{loading ? ( <p>Fetching todo item {id}</p>
) : ( <div> <h2 className="todo-title">{title}</h2> <h4>Added by: {userId}</h4> {completed ? ( <p className="completed">This item has been completed</p> ) : ( <p className="not-completed">This item is yet to be completed</p> )} </div>
)}

Once this is done all tests should now pass. Yay! We have another winner.

Our component tests now pass. But we still haven’t added it to our main app. Let’s do that.

Open src/App.js and add the import line:

import { TodoItem } from './TodoItem'

Add the TodoItem route above the TodoList route. Be sure to preserve the order shown below.

# preserve this order
<Route path="/item/:id" component={TodoItem} />
<Route exact path="/" component={TodoList} />

Open your project in your browser and click on a to-do. Does it take you to the to-do page? Of course, it does. Good job.

In case you’re having any problem, you can check out my code at this point from the 04-test-todo branch.

Phew! This has been a marathon. But bear with me. There’s one last point I’d like us to touch. Let’s quickly have a test case for when a user visits our app, and then proceed to click on a to-do link. This is a functional test to mimic how our app should work. In practice, this is all the testing we need to be done for this app. It ticks every box in our app specification.

Open App.test.js and add a new test case. The code is a bit long so we’ll add it in two steps.

import userEvent from "@testing-library/user-event";
import { todos } from "./makeTodos"; jest.mock("react-router-dom", () => ({ ...jest.requireActual("react-router-dom"),
})); describe("<App />" ... // previous test case ... it("Renders todos, and I can click to view a todo item", async () => { render(<App />); await waitForElementToBeRemoved(() => screen.getByText(/Fetching todos/i)); todos.slice(0, 15).forEach((td) => { expect(screen.getByText(td.title)).toBeInTheDocument(); }); // click on a todo item and test the result const { id, title, completed, userId } = todos[0]; axios.get.mockImplementationOnce(() => Promise.resolve({ data: { id, title, userId, completed }, }) ); userEvent.click(screen.getByTestId(String(id))); await waitForElementToBeRemoved(() => screen.getByText(`Fetching todo item ${String(id)}`) ); // next code block goes here });
});

We have two imports of which userEvent is new. According to the docs,

user-event is a companion library for the React Testing Library that provides a more advanced simulation of browser interactions than the built-in fireEvent method.”

Yes. There is a fireEvent method for simulating user events. But userEvent is what you want to be using henceforth.

Before we start the testing process, we need to restore the original useParams hooks. This is necessary since we want to test actual behavior, so we should mock as little as possible. Jest provides us with requireActual method which returns the original react-router-dom module.

Note that we must do this before we enter the describe block, otherwise, Jest would ignore it. It states in the documentation that requireActual:

“…returns the actual module instead of a mock, bypassing all checks on whether the module should receive a mock implementation or not.”

Once this is done, Jest bypasses every other check and ignores the mocked version of the react-router-dom.

As usual, we render the <App /> component and wait for the Fetching todos loading indicator to disappear from the screen. We then check for the presence of the first 15 to-do items on the page.

Once we’re satisfied with that, we grab the first item in our to-do list. To prevent any chance of a URL collision with our global Axios mock, we override the global mock with Jest’s mockImplementationOnce. This mocked value is valid for one call to the Axios get method. We then grab a link by its data-testid attribute and fire a user click event on that link. Then we wait for the loading indicator for the single to-do page to disappear from the screen.

Now finish the test by adding the below expectations in the position indicated.

expect(screen.getByText(title)).toBeInTheDocument();
expect(screen.getByText(`Added by: ${userId}`)).toBeInTheDocument();
switch (completed) { case true: expect( screen.getByText(/This item has been completed/) ).toBeInTheDocument(); break; case false: expect( screen.getByText(/This item is yet to be completed/) ).toBeInTheDocument(); break; default: throw new Error("No match"); } 

We expect to see the to-do title and the user who added it. Finally, since we can’t be sure about the to-do status, we create a switch block to handle both cases. If a match is not found we throw an error.

You should have 6 passing tests and a functional app at this point. In case you’re having trouble, the corresponding branch in my repo is 05-test-user-action.

Conclusion

Phew! That was some marathon. If you made it to this point, congratulations. You now have almost all you need to write tests for your React apps. I strongly advise that you read CRA’s testing docs and RTL’s documentation. Overall both are relatively short and direct.

I strongly encourage you to start writing tests for your React apps, no matter how small. Even if it’s just smoke tests to make sure your components render. You can incrementally add more test cases over time.

Smashing Editorial(ks, ra, yk, il)