Our Frontend team recently introduced React into a large scale SAAS product. By the end of the implementation we had about 15 live components including reusable ones like buttons and spinners, and larger ones that contained most of the app’s logic.

In order to allow for quicker development we chose to implement all of the components without any unit or integration tests. All of the components we wrote were replacing existing functionality which meant they all had end-to-end UI test coverage. We felt confident that these UI tests would be enough until we went back to write more in-depth coverage.

Deciding on Jest

Choosing Jest was quite simple. We did a quick comparison on a few React testing technologies and found that Jest would support our needs the best. It is officially supported by the Facebook team and a few key features such as mocking, parallelizing test runs, and snapshots seemed like they could help us with problems we experienced in the past. Once we got started on the implementation, the setup and config were extremely simple and the documentation was extensive. Our team discussed that we would need other utilities and libraries to help us specifically test React components. We briefly looked at using Enzyme but since the documentation and principles around React Testing Library looked so promising we didn’t explore Enzyme further.

React Testing Library

React Testing Library (react-testing-library) was probably the most important discovery in this whole process. React Testing Library is a library that works well with Jest and it really challenges you to think hard about what exactly you are testing. In the past, our team struggled to find the line between too much test coverage and not enough. The principle behind React Testing Library is the following:

You would think that this is obvious but many of our previously written Javascript tests test the implementation of a function rather than the result (this might not be as important in a traditional project with separated HTML, CSS, and Javascript files but it is crucial in a React project). React Testing Library also pushes you to write more integration tests and fewer unit tests. This is important because a React app can be made of many components. Testing an individual component is important but testing how all these components work together is arguably more important. Your users do not care about how your app is structured or what technology you are using. They care that they can interact with your app and that it works seamlessly.

Traditional testing pyramid

Snapshot testing

Snapshot testing is an intriguing feature of Jest that allows you to test Javascript objects. It works well with React components because when you render a component you can view the DOM output and create a “snapshot” at the time of run. When I started looking into them there were many divided opinions in the community. The more useful aspect of these tests is that they protect against regression really well. The downside is that sometimes you can’t tell what is being tested, and you could fall into a bad habit of updating snapshot tests blindly. We have a few components that have snapshot tests but in general I would not recommend them over specific assertions. If your component does not update often, is not complex, and is easy to see exactly what you are testing, then a snapshot test might work.

Implementation

The team began writing tests and found ourselves having long discussions about what should be tested. When we couldn’t agree on a solution we always came back to, “Does this help test how our users use this application?”. After thinking about this, it would become more clear that integration style tests were more valuable (in most cases) than unit style tests. Throughout this process we were able to define a more concrete answer to when to write different tests. Reusable utilities would still have unit test coverage but a utility that lived inside a component would be covered by an integration test.

Here is an example of a component test that renders a region with multiple region items. It reads data from a json file, iterates through each item and then renders it to the DOM. There are a few helper methods to construct the href and filter through regions but we only are testing what was rendered in the component.

import React from 'react'

import { render, fireEvent } from 'react-testing-library'

import Component from './Container' describe('<Component /> spec', () => { it('renders the component', () => {

const container = render(<Component />)

expect(container.firstChild).toMatchSnapshot()

}) it('assert there are 6 regions', () => {

expect(document.querySelectorAll('.map-region').length).toBe(6)

}) it('assert there are 12 region items', () => {

expect(document.querySelectorAll('.region-item').length).toBe(12)

}) it('assert connect button renders the correct label', () => {

expect(document.querySelector('.connect-btn').innerHTML).toBe("Connect")

}) it('assert the first item link to be /somelink', () => {

const allAgent = document.querySelector('.region-item:first-child a')

expect(allAgent.getAttribute('href')).toBe('/somelink?id=123&name=link')

}) });

Overall, I really enjoyed working with Jest and React Testing Library. It seems like a simple library at first but it is very valuable as it tests a complete integration with your React components.

Key takeaways

When testing a React component, keep in mind:

Do the tests I am proposing to write help prevent regression?

Do these tests test how a user will interact with this feature?

If you find it difficult to test your React components they should probably be rewritten.

Test driven development has always seemed like a great practice to follow and perhaps with Jest and React Testing Library, it may be easier to achieve.