React Component Testing

Testing Libraries

We use two main testing libraries, enzyme and react-testing-library. We have a preference for react-testing-library for new code.

Enzyme

You should use enzyme if needed; leave a comment as to why you’re using it.

What it’s good at

  • letting you change the component state directly (without a simulated click). However, this is not a pattern that we want to encourage in testing, because users can’t directly manipulate state or props, so we don’t want our tests to.

  • letting you introspect what is in the component’s state / props

  • pure unit testing. If you’re using a library like Paragon, and just want to test that you’re passing the correct props to the component, enzyme might be a good choice

  • Testing react class components - allows you to unit test class methods by calling them directly

Disadvantages

  • It can encourage bad testing patterns by allowing the developer to directly manipulate state

  • It can encourage bad testing patterns by allowing users to introspect state / props rather than test what the user is seeing in the DOM

Examples

react-testing-library

This is our preferred testing library

What it’s good at

  • Integration testing

  • Easy to access components via what the user would see in the dom

  • Testing React functional components

Disadvantages

  • More difficult to check if you’re passing props correctly / introspect on component’s inner working

  • Difficult to unit test class components

Snapshot testing

Snapshot testing is inherently brittle, and as such, is good for testing components that will rarely, if ever change. Example of a good candidate for snapshot testing

Mocking

Mocking external libraries

There are several ways to do this with Jest. If only a small part of the library needs to be mocked, the simplest way is to mock it in your test file.

jest.mock('redux-form', () => ({ ...jest.requireActual('redux-form'), // eslint-disable-next-line react/prop-types Field: ({ label, ...rest }) => <div {...rest}>{label}</div>, }));

In the above example, I want all of the functionality of redux-form, but the Field components are not under test, and have functionality that I don’t need here, so I’m mocking them and leaving the other functionality alone by using jest.requireActual.

The other way to mock an external library is by using a manual mock that mocks all parts of the library that you want to mock. This is especially helpful for libraries such as Algolia that call external APIs.
Algolia mock example

Mocking a module

Jest documentation for mocking modules and classes

Often you may want to mock a module in the repo you’re working in, such as an API. There’s some extra syntax for a jest mock that is covering a module:

import LicenseManagerApiService from '../../data/services/LicenseManagerAPIService'; jest.mock('../../data/services/LicenseManagerAPIService', () => ({ __esModule: true, default: { fetchSubscriptions: jest.fn(), }, }));

In the above example, the default value of '../../data/services/LicenseManagerAPIService' is mocked. Under the hood, Jest uses variable hoisting to ensure that the mock of the module actually happens before the import, allowing us to use the mock as we normally would, in this case, mocking a return value from the API:

const subscriptions = Promise.resolve({ data: { results: { subscriptions: [sub1, sub2], }, errors: null, setErrors: jest.fn(), forceRefresh: jest.fn(), loading: false, }, }); LicenseManagerApiService.fetchSubscriptions.mockReturnValue(subscriptions);

To mock a non-default part of a module, code like this can be used:

 

Some common issues in testing React code

‘act’ warnings: These often as a symptom that something unexpected is happening, even if your test generally will pass despite these. Check this very useful article on how to address these: https://kentcdodds.com/blog/fix-the-not-wrapped-in-act-warning?ck_subscriber_id=1215839415 . These warnings will look typically like this:

Testing philosophy

How to avoid brittleness in tests

  • Rather than using CSS-selectors, prefer first text in the elements (use constants so that tests don’t have to change when text does), then data test ID attributes, so that you can select certain elements in a more reliable way in the course of testing. (This is particularly easy with react-testing-library which has a getByTestId method)

  • Put microcopy in exported variables and use those in your tests so your tests don’t break every time you change the microcopy

Coverage

  • Start with a threshold that will pass today, and rachet it up over time, say, 10% per quarter

  • Use tools to fail a build when coverage decreases, such as coveralls (used in Paragon). This encourages developers to write tests for all new code.