Test Driven Development in React with Jest and Enzyme
By: Ross Bulat
Tags: Software Engineering, JavaScript, Test Driven Development, Programming, React

Structuring React Components for Testing

React developers accustomed to developing components will know that component logic can build up very fast as state changes, API calls and other logic like mappings are introduced. What we want to do to this logic is ensure that it works under all the circumstances it will be used with. To do this, we want to achieve 2 things:

1. Test the logic in an isolated manner

Abstracting code becomes a primary concern in TDD, which also indirectly promotes principles such as DRY (Don’t Repeat Yourself) for maximum component reusability. This is easily achieved in React with the flexibility of components.

Consider an example project, a navigation app with a <SideBar /> and <TopBar /> component — both of which use an <Item /> component for navigation links. By separating each component (and their dedicated styling), we are isolating the logic as well as the tests associated with each component.

Remember, TDD with component isolation in mind would limit a component’s purpose to do just one thing, and reject the notion of it doing anything else but that thing.

Check out this hypothetical navigation app project structure:

// @rossbulat/nav package

src/

Item/

index.js

index.scss

SideBar/

index.js

index.scss

TopBar/

index.js

index.scss

tests/

Item.test.js

nav.test.js

SideBar.test.js

TopBar.test.js // @rossbulat/app

src/

App.js

api.js

Home/

index.js

index.scss tests/

app.test.js

api.test.js // src/Home/index.js

import { SideBar, TopBar } from '@rossbulat/nav';

For ultimate abstract-ability, the navigation components actually come as a package, @rossbulat/nav , allowing me to include them in more projects to serve navigation needs. This warrants the usage of abstract component names like <Item /> as well as every component to be listed one level down under src/ . With these components totally isolated, and with the testing suite embedded within the package, our navigation is much easier to manage.

Separating both <SideBar /> and <TopBar /> ensures that I can test each component independently. Not only this, each navigation link consists of an <Item /> component; this component may include props like title , link , icon , and more — all of which can be tested in an isolated manner.

As well as my separate component tests, I also have a nav.test.js file that can include my integration tests, or all 3 components working together, to make sure they adhere to an expected snapshot — perhaps checking how many <li> tags are nested within <SideBar /> markup to coincide with how many <Item /> components are present. We will visit snapshots further down.

My API is also completely separate from other components within my main app project; the api.test.js file is specifically for API calls, which will no doubt include various mock functions to serve fake API calls. Again, we will explore mocks further down this talk.

2. Test the code within a range of scenarios

This can only be achieved if the prior condition of abstracting logic is met, whereby an extensible range of props can be passed into the component, or in the case of integration testing, an extensible number of other components that work together.

With the power of isolation in mind, let’s next explore some tangible code examples of testing React components.

Perhaps the simplest of ways to test a React component is to compare the result of a render function with a snapshot file. Jest provide us with tools necessary for doing exactly this.

Using Snapshots with Jest

Using Jest’s terminology, snapshot testing is a useful feature to make sure that your markup does not unexpectedly change, and equally as true, makes sure that render() outputs what you intended it to.

Testing snapshots against components can be done with Enzyme methods (which we will see below) but can also be used with the react-test-renderer package (extremely popular with over 2 million weekly downloads at publishing time). Add it to your project via NPM or yarn:

yarn add react-test-renderer

A snapshot file is automatically generated when the toMatchSnapshot() method is called within your tests, often in conjunction with expect() in the following form:

expect(<component>).toMatchSnapshot();

The contents of a snapshot file mostly consists of markup that represents the expected output of your React component. Here is an example .snap file that Facebook have provided, that tests different link states.

Even though we would not manually edit a snapshot file under normal circumstances, they are easy to read by design for the purpose of code reviewing, and are designed to be added to source control — commit your snapshot files with your code updates.

Let’s visit a practical example of a snapshot test — I will test whether my <Item /> component from earlier renders correctly. Note that running the test for the first time will generate the snapshot file:

import React from 'react'

import { Item } from '@rossbulat/nav'

import renderer from 'react-test-renderer'

import icon from './img/ross.png' describe('testing navigation', () => {

it('renders correctly', () => {

const item = renderer

.create(

<Item

link="https://rossbulat.co"

text="My Homepage"

icon={icon} />

).toJSON(); expect(item).toMatchSnapshot();

});

});

This next example using Enzymes shallow() method, in this case testing whether my <SideBar /> component will render correctly against a snapshot:

...

it('sidebar should render correctly', () => {

const sidebar = shallow(<SideBar />);

expect(sidebar).toMatchSnapshot();

});

I could also test whether <SideBar /> renders correctly given an array of links:

it('sidebar should render links correctly', () => {

const links =

[{link: 'https://rossbulat.co', text: 'My Homepage'},

{link: 'https://medium.com/@rossbulat', text: 'Medium'}]; const component = shallow(<MyComponent links={links} />);

expect(component).toMatchSnapshot();

});

This test will generate another snapshot. Concretely, each of my tests will generate a different snapshot file, even if the same component is being used.

If you are running your tests in watch mode, with yarn test --watch , followed by pressing i for interactive snapshot mode, failed snapshots will pop up as they happen and the exact failure will be documented, allowing you to troubleshoot inconsistencies.

Snapshot files are generated in a __snapshots__ folder under your test directories, all files of which will be named <test_name>.snap .

Note: The package used to convert Javascript objects into string representations within these .snap files is pretty-format. The package is just another NPM package, and can be used in your own projects where you wish to debug or document Javascript implementations.

If after you have updated your UI and wish to update your snapshots, we have a handy command to do just that. Run the following in your project directory to update all your snapshots:

jest --updateSnapshot

You will want to update your snapshots as you are enhancing your components. Make sure your initial snapshot outputs are as expected before running subsequent tests against them.

For the full documentation and feature set of snapshots in Jest, visit this page.

Simulating Events

Simulating events such as clicks and inputting text can be done within our tests using Enzyme’s simulate() method. For example, let’s say I wanted to test the result of clicking a button inside a component. This can be done like so:

it('should update form submitted state with button click', () => {

const component = mount(<RegistrationForm />);

component

.find('button#submit_form')

.simulate('click');



expect(component.state('form_submitted')).toEqual(true);

component.unmount();

});

Here we are testing a component, <RegistrationForm /> , and the resulting state after clicking the button with id #submit_form . As demonstrated, component state can be accessed component.state('<key>') , and then compared with a matcher with expect() .

In reality, we would probably need a form to be filled before submitting a registration form, at least with an email address or phone number. Can we easily test input values? Yes we can, like so:

component

.find('#name')

.simulate('change', { target: { value: 'Ross' } });

Similarly, we can also toggle checkboxes with simulate() :

component

.find('#agreetoterms')

.simulate('change', {target: {checked: true}});

And even simulate a key press via a keycode (refer to keycodes here):

component.find('#input').simulate('keydown', { keyCode: 70 });

You may also wish to call component methods before testing state, snapshots or anything else. We can access component methods by its .instance() property:

const component = shallow(<MyComponent />);

const result = component.instance().callMethod();

Using Mock Functions

As the name suggests, Mock functions allow us to re-implement a function, stripping away logic for the purpose of capturing calls to the function, testing parameters, and testing return values.

The simplest way to use mock functions is to simply define an empty function and put it in place of an actual function within your tests. The benefit of doing this is tracking whether the function was actually called. (How many times have you been debugging and noticed that a particular function was not actually called, leading to the error?).

We can define such a function by using jest.fn() . This is how we would replace an onClick handler with an empty mock function, and test whether it has been called:

//define empty mock function

const fnClick = jest.fn(); describe('click events', () => {

it('button click should show menu', () => { //replace actual function with mock function

const component = shallow(<MyButton onClick={fnClick} />);



//simulate a click

component

.find('button#btn_open_menu')

.simulate('click');



//check if function was called

expect(fnClick).toHaveBeenCalled();

});

});

The matcher .toHaveBeenCalled() is only one of a few we can test with mocks. Here are a few more:

// Test how many times the function is called

expect(fnClick.mock.calls.length).toBe(3); // Test the values passed as arguments

// The second argument of the third call to the function was 'yes'

expect(mockCallback.mock.calls[2][1]).toBe('yes'); // Test return values

// The return value of the second call to the function was true

expect(mockCallback.mock.results[1].value).toBe(true);

Here we have tapped into the .mock property of a mock function, giving us access to information about how the function has been called, and what has been returned. Read more about it here.

Injecting Mock Functions

Another cool thing we can do with mocks is inject them in a test whenever we wish to retrieve the return value, with console.log() :

const myMock = jest.fn();

console.log(myMock('return this string')); // > return this string

We can also direct a mock function to return specific values before calling it within the test. This is handy to simulate a range of return values as proof your app can handle them:

// return `true` for first call,

// return `false` for the second call

// return a string 'hello mock' for the third call myMock.mockReturnValueOnce(true)

.mockReturnValueOnce(false)

.mockReturnValueOnce('hello mock'); //call the mock three times to witness the results:

console.log(myMock(), myMock(), myMock());

This is useful for retrieving data and simulating form submissions where there is arbitrary data being supplied. Test whether your app can handle an undefined value for example, along with a range of string lengths for input fields, as well as unexpected values, e.g. An object where a boolean value is expected.

Imagine a scenario where a JSON string {result: true} is returned instead of true . The JSON string itself is s truthy value, with a value of {result: false} also evaluating as truthy. Testing return values with mocks will ensure these scenarios do not happen.

Mock functions can be used in conjunction with everything we have explored so far, including snapshots and simulating events. You will find your tests becoming more sophisticated as more scenarios are covered. You are of course free to actually implement mock functions:

const myMock = jest.fn(() => {

...

});

The general rule of doing so is to not introduce arbitrary logic to the original function. What if one implementation is not enough — what if we would like to test multiple implementations of the same function? This can be done with mockImplementationOnce(() => {...}) , taking the implementation itself as an argument:

//define implementations const myMockFn = jest.fn(() => 'default return value')

.mockImplementationOnce(() => {

...

return 'first implementation';

})

.mockImplementationOnce(() => {

...

return 'second implementation';

}); //call function

console.log(myMockFn(), myMockFn()); //any calls hereafter will refer to the default implementation.

Check out more on mock implementations here.

Modules can also be mocked using jest.mock() . Think database calls or API requests that are slow and fragile — things could break quite easily making for an unreliable test. Jest provide a simple example of mocking the axios module to overwrite API calls, but this is the general gist of how to mock a module function:

// import the module to mock

import axios from 'axios'; // wrap the module in jest.mock()

jest.mock('axios'); test('should fetch users', () => {

const users = [{first_name: 'Ross'}];

const resp = {data: users}; // append .mockResolvedValue(<return value>) to the module method

axios.get.mockResolvedValue(resp); // carry out your test

return expect(resp.data).toEqual(users));

});

This is quite an elegant solution to bypassing module methods; by overwriting the functionality with Jest — .mockResolvedValue(<return value>) allows us to do exactly this.

Debugging components

It is worth mentioning here that if you wish to log a component’s render() output in HTML like fashion, use the debug() method of a wrapper:

const component = shallow(<MyComponent />);

console.log(component.debug());

Debugging components within your tests can quickly pinpoint why a test is failing.

Summary

In conjunction with my original introduction on testing, we have covered enough in this talk to get into serious React testing suites, with a range of methods that when used together can provide a comprehensive means of testing components.

Have we touched everything? No — but these powerful tools will kickstart your testing suite with Jest and Enzyme:

Snapshots: allowing you to compare render() output of components to an expected result.

allowing you to compare output of components to an expected result. Simulations: carry out tests that involve the events you would expect in the browser — click events, form inputs and function calls.

carry out tests that involve the events you would expect in the browser — click events, form inputs and function calls. Mock functions: Test arguments and return values with empty mock functions, or carry out a range of implementations to test the expected result. Also, overwrite fragile module methods that rely on external data sources, or involve heavy processing that would slow down your tests.

Test arguments and return values with empty mock functions, or carry out a range of implementations to test the expected result. Also, overwrite fragile module methods that rely on external data sources, or involve heavy processing that would slow down your tests. Abstract-ability: keep logic isolated and tied into specific components, promoting re-usability, DRY and wide-ranging integration testing.

How will you develop your tests?

The tools we have discussed here are used with BDD and TDD. I personally think that TDD can be adopted for the majority of application projects, but is particularly suited for long term, protocol based projects, API services, critical package and infrastructure projects, as well as cutting-edge research projects. In agile development, perhaps opting for a particular method depending on your sprint goals will make sense for your team.

What would you prefer — a BDD approach of creating your testing suite after achieving your desired app behaviour? Or a TDD approach of integrating tests as your components are being developed, plugging in the final behavioural goal after your components are fully test-driven. Let me know!