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
Using enzyme for unit tests in Paragon
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.
Â