Skip to content

The React Testing Library Guide I Wish I Had

December 21, 2021Elise RECEJAC6 min read

React Testing Library

I would like to share with you a great library I have found, which makes front-end tests fun to write!

With this article, you will be able to write serenely your first tests with React Testing Library. To have a deeper understanding of the library you can use the documentation.

Is it really useful to write front-end tests?

The problem is that as your app grows you cannot be sure that everything still works properly each time you add some new code. Seems quite obvious, right? Then, why do so few people write front-end tests?

When I asked some people about it, they answered: "we don't have enough time", "it's not really useful"...

The first thing you should know is that, yes, at the beginning it seems that tests are not worth the time. But as your app grows it will be more and more difficult to implement them and your app will be more and more unstable. Then, you should implement tests from the beginning of a project: it is easier and it will be timesaver later.

The second thing to know: tests are definitely useful. A quick anecdote to convince you: the first test I have ever written spotted a bug.

Then it's time to learn how to test!

Why use React Testing Library?

Now that you are - I hope - convinced that you should test your front-end code, what library should you use?

My first advice is: don't use snapshots, as this article explains. To sum it up: snapshots are difficult to understand for developers and then it doesn't really test the app the way users use it.

When I implemented front-end tests on my project I chose React Testing Library. React Testing Library allows you to test by simulating human interactions with the UI, while being quite independent of the implementation.

Indeed, the closer the test is to the actual app usage, the more relevant it is.

As a bonus, using React Testing Library neatly can actually improve the accessibility of your app (See an article about accessiblity improvement to understand why).

How to test with React Testing Library?

The principle of React Testing Library is to render a component and then simulate some interaction with it, and then finally compare the result with the expectation.

1. Render a component

The first thing you have to do is to get the component you want to test. It can be a whole page or isolated components. The best thing to do is to find the right balance between testing a page with a lot of components, which can be long because of nested components but is closer to the user experience, and testing components, which is faster but sometimes less relevant.

Anyway, you should never test already tested components and behavior. For example, components from libraries are usually tested and you shouldn't test them again.

The easiest way to get a component is to use directly the render method:

import { render } from "@testing-library/react";
import MyComponent from "../MyComponent";

test('My first test', async () => {
	render(<MyComponent />);
}

It is also possible to wrap the render function in a provider to mock some features, such as the Redux store or the internationalization:

import * as React from "react";

import {
  RenderResult,
  render as rtlRender,
  RenderOptions as RtlRenderOptions,
} from "@testing-library/react";
import { IntlProvider } from "react-intl";
import { Provider } from "react-redux";
import flattenMessages from "services/i18n/intl";
import frMessages from "translations/fr.json";

import rootSaga from "../redux/sagas-for-tests";

const defaultMessages = flattenMessages(frMessages);

const render = (
  ui: React.ReactElement,
  {
    initialState,
    store = storeCreator(initialState), // custom function to create the store
    messages = defaultMessages,
    ...renderOptions
  }: RenderOptions
): RenderResult => {
  const Wrapper: React.ComponentType = ({ children }) => {
    return (
      <Provider store={store}>
        <IntlProvider messages={messages} locale="fr">
          {children}
        </IntlProvider>
      </Provider>
    );
  };
  return rtlRender(ui, { wrapper: Wrapper, ...renderOptions });
};

With a custom render function like this you can render your component with a mocked state:

import { mockState } from '__fixtures__/state';
import { render } from 'utils/test-utils'; //custom render
	import MyComponent from "../MyComponent";

test('My first test with a mock state', async () => {
	render(<MyComponent />, { initialState: mockState });
}

Now that you can display a component, let us play a bit with it.

2. Simulate user interactions

To interact with the elements of a component you will need a way to access them. You have several ways to do this. You can, for example, query elements by text, label, placeholder, alternative text, or role. Regardless of what attribute you use you have three main methods to get an element.

Generally, I prefer to use getBy, which is the most simple one. But if you need to get the element asynchronously you can use findBy. It can be useful when you wait the results of an asynchronous method for example. If you want to check if an element is not in the DOM, I advise you to use queryBy, which returns "null" if the element was not found.

You should know that all of these methods will throw if more than one element matches, though you can overcome this limitation by using getAllBy, findAllBy, or queryAllBy to get an array.

Have a look at how you would query for a button reading “press me”:

import { render } from "@testing-library/react";
import MyComponent from "../MyComponent";

test('Get button test', async () => {
	const { getByRole } = render(<MyComponent />);
	const pressMeButton = getByRole("button", { name: "press me" });
}

Once you have your button you can interact with it, by simulating a click on it:

import { render } from "@testing-library/react";
import MyComponent from "../MyComponent";
import user from "@testing-library/user-event";

test('Click on button test', async () => {
	const { getByText } = render(<MyComponent />);
	const pressMeButton = getByText("press me");
	user.click(pressMeButton);
}

Another classical way to interact with an element is when the user types something in an input field. This interaction can also be easily simulated. With this code, for example, you would simulate a user typing "some random text" in an input field labeled "myInputField':

import { render } from "@testing-library/react";
import MyComponent from "../MyComponent";
import user from "@testing-library/user-event";

test('Fill input test', async () => {
	const { getByText } = render(<MyComponent />);
	const myInputField = getByLabelText("myInputField");
	user.type(myInputField, "some random text");
}

Sometimes you will try to get an element, but you will have an error message saying that it was not found. To help you debug that, React Testing Library has a wonderful method...

3. Debug

It is really easy to debug with React Testing Library. Indeed, it provides a method, debug, to display the entire DOM at a specific moment.

import { render } from "@testing-library/react";
import MyComponent from "../MyComponent";
import user from "@testing-library/user-event";

test('Debug fill input test', async () => {
	const { getByText, debug } = render(<MyComponent />);
	const myInputField = getByLabelText("myInputField");
	debug(); //displays the DOM with an empty input
	user.type(myInputField, "some random text");
	debug(); //displays the DOM with the input filled with "some random text"
}

4. Examine the result

Now that you are able to render a component and interact with it the way the user would do, it is time to check that everything works as expected.

To do that you just have to use the expect method, for example, to check that an input has the expected text content:

import { render } from "@testing-library/react";
import MyComponent from "../MyComponent";
import user from "@testing-library/user-event";

test('Debug fill input test', async () => {
	const { getByText } = render(<MyComponent />);
	const myInputField = getByLabelText("myInputField");
	user.type(myInputField, "some random text");
	expect(myInputField).toHaveTextContent("some random text");
}

5. Mocks

Finally, you should know that you can mock the different API calls and hooks using Jest to fully test your app. Using mocks enables you to test highly complex functions. Moreover, it enables you to have control over the data.

Conclusion

Testing the front-end with React Testing Library improves the quality of your code itself, it enables you to test the user interactions and above all, it is quite intuitive. Now you no longer have any excuse to avoid testing your front-end!

Elise RECEJAC

Elise RECEJAC

Web Developer at Theodo