Web Front-end7 minute read

Test-driven React.js Development: React.js Unit Testing with Enzyme and Jest

Any piece of code that has no tests is said to be legacy code according to Michael Feathers. Therefore, one of the best ways to avoid creating legacy code is using test-driven development (TDD).

While there are many tools available to create unit tests in JavaScript, in this post, we will use Jest and Enzyme to create a React.js component with basic functionality using TDD.


Toptalauthors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.

Any piece of code that has no tests is said to be legacy code according to Michael Feathers. Therefore, one of the best ways to avoid creating legacy code is using test-driven development (TDD).

While there are many tools available to create unit tests in JavaScript, in this post, we will use Jest and Enzyme to create a React.js component with basic functionality using TDD.


Toptalauthors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.
Alonso Ayala Ortega
Verified Expert in Engineering

Over the last decade, Alonso’s Oracle certifications and full-stack work have lately turned toward QA automation and sharp BDD solutions.

Read More

PREVIOUSLY AT

Netflix
Share

Any piece of code that has no tests is said to be legacy code, according to Michael Feathers. Therefore, one of the best ways to avoid creating legacy code is using test-driven development (TDD).

While there are many tools available for JavaScript and React.js unit testing, in this post, we will use Jest and Enzyme to create a React.js component with basic functionality using TDD.

Why Use TDD to Create a React.js Component?

TDD brings many benefits to your code—one of the advantages of high test coverage is that it enables easy code refactoring while keeping your code clean and functional.

If you have created a React.js component before, you’ve realized that code can grow really fast. It fills up with lots of complex conditions caused by statements related to state changes and service calls.

Every component lacking unit tests has legacy code that becomes difficult to maintain. We could add unit tests after we create the production code. However, we may run the risk of overlooking some scenarios that should have been tested. By creating tests first, we have a higher chance of covering every logic scenario in our component, which would make it easy to refactor and maintain.

How Do We Unit Test a React.js Component?

There are many strategies we can use to test a React.js component:

  • We can verify that a particular function in props was called when certain a event is dispatched.
  • We can also get the result of the render function given the current component’s state and match it to a predefined layout.
  • We can even check if the number of the component’s children matches an expected quantity.

In order to use these strategies, we are going to use two tools that come in handy to work with tests in React.js: Jest and Enzyme.

Using Jest to Create Unit Tests

Jest is an open-source test framework created by Facebook that has a great integration with React.js. It includes a command line tool for test execution similar to what Jasmine and Mocha offer. It also allows us to create mock functions with almost zero configuration and provides a really nice set of matchers that makes assertions easier to read.

Furthermore, it offers a really nice feature called “snapshot testing,” which helps us check and verify the component rendering result. We’ll use snapshot testing to capture a component’s tree and save it into a file that we can use to compare it against a rendering tree (or whatever we pass to the expect function as first argument.)

Using Enzyme to Mount React.js Components

Enzyme provides a mechanism to mount and traverse React.js component trees. This will help us get access to its own properties and state as well as its children props in order to run our assertions.

Enzyme offers two basic functions for component mounting: shallow and mount. The shallow function loads in memory only the root component whereas mount loads the full DOM tree.

We’re going to combine Enzyme and Jest to mount a React.js component and run assertions over it.

TDD steps to create a react component

Setting Up Our Environment

You can take a look at this repo, which has the basic configuration to run this example.

We’re using the following versions:

{
  "react": "16.0.0",
  "enzyme": "^2.9.1",
  "jest": "^21.2.1",
  "jest-cli": "^21.2.1",
  "babel-jest": "^21.2.0"
}

Creating the React.js Component Using TDD

The first step is to create a failing test which will try to render a React.js Component using the enzyme’s shallow function.

// MyComponent.test.js
import React from 'react';
import { shallow } from 'enzyme';
import MyComponent from './MyComponent';
describe("MyComponent", () => {
  it("should render my component", () => {
    const wrapper = shallow(<MyComponent />);
  });
});

After running the test, we get the following error:

ReferenceError: MyComponent is not defined.

We then create the component providing the basic syntax to make the test pass.

// MyComponent.js
import React from 'react';

export default class MyComponent extends React.Component {
  render() {
    return <div />;
  }
}

In the next step, we’ll make sure our component renders a predefined UI layout using toMatchSnapshot function from Jest.

After calling this method, Jest automatically creates a snapshot file called [testFileName].snap, which is added the __snapshots__ folder.

This file represents the UI layout we are expecting from our component rendering.

However, given that we are trying to do pure TDD, we should create this file first and then call the toMatchSnapshot function to make the test fail.

This may sound a little confusing, given that we don’t know which format Jest uses to represent this layout.

You may be tempted to execute the toMatchSnapshot function first and see the result in the snapshot file, and that’s a valid option. However, if we truly want to use pure TDD, we need to learn how snapshot files are structured.

The snapshot file contains a layout that matches the name of the test. This means that if our test has this form:

desc("ComponentA" () => {
  it("should do something", () => {
    …
  }
});

We should specify this in the exports section: Component A should do something 1.

You can read more about snapshot testing here.

So, we first create the MyComponent.test.js.snap file.

//__snapshots__/MyComponent.test.js.snap
exports[`MyComponent should render initial layout 1`] = `
Array [
<div>
    <input
         type="text"
    />
</div>,
]
`;

Then, we create the unit test that will check that the snapshot matches the component child elements.

// MyComponent.test.js
...
it("should render initial layout", () => {
    // when
    const component = shallow(<MyComponent />);
    // then
    expect(component.getElements()).toMatchSnapshot();
});
...

We can consider components.getElements as the result of the render method.

We pass these elements to the expect method in order to run the verification against the snapshot file.

After executing the test we get the following error:

Received value does not match stored snapshot 1.
Expected:
 - Array [
    <div>
        <input type="text” />
     </div>,
    ]
Actual:
+ Array []

Jest is telling us that the result from component.getElements does not match the snapshot. So, we make this test pass by adding the input element in MyComponent.

// MyComponent.js
import React from 'react';
export default class MyComponent extends React.Component {
  render() {
    return <div><input type="text" /></div>;
  }
}

The next step is to add functionality to input by executing a function when its value changes. We do this by specifying a function in the onChange prop.

We first need to change the snapshot to make the test fail.

//__snapshots__/MyComponent.test.js.snap
exports[`MyComponent should render initial layout 1`] = `
Array [
<div>
    <input
         onChange={[Function]}
         type="text"      
     />
</div>,
]
`;

A drawback of modifying the snapshot first is that the order of the props (or attributes) is important.

Jest will alphabetically sort the props received in the expect function before verifying it against the snapshot. So, we should specify them in that order.

After executing the test we get the following error:

Received value does not match stored snapshot 1.
Expected:
 - Array [
    <div>
        onChange={[Function]}
        <input type="text”/>
     </div>,
    ]
Actual:
+ Array [
    <div>
        <input type=”text”  />
     </div>,
    ]

To make this test pass, we can simply provide an empty function to onChange.

// MyComponent.js
import React from 'react';

export default class MyComponent extends React.Component {
  render() {
    return <div><input
      onChange={() => {}}
      type="text" /></div>;
  }
}

Then, we make sure that the component’s state changes after the onChange event is dispatched.

To do this, we create a new unit test which is going to call the onChange function in the input by passing an event in order to mimic a real event in the UI.

Then, we verify that the component state contains a key named input.

// MyComponent.test.js
...
it("should create an entry in component state", () => {
    // given
    const component = shallow(<MyComponent />);
    const form = component.find('input');
    // when
    form.props().onChange({target: {
       name: 'myName',
       value: 'myValue'
    }});
    // then
    expect(component.state('input')).toBeDefined();
});

We now get the following error.

Expected value to be defined, instead received undefined

This indicates that the component doesn’t have a property in the state called input.

We make the test pass by setting this entry in the component’s state.

// MyComponent.js
import React from 'react';

export default class MyComponent extends React.Component {
  render() {
    return <div><input
      onChange={(event) => {this.setState({input: ''})}}
      type="text" /></div>;
  }
}

Then, we need to make sure a value is set in the new state entry. We will get this value from the event.

So, let’s create a test that makes sure the state contains this value.

// MyComponent.test.js
...
  it("should create an entry in component state with the event value", () => {
    // given
    const component = shallow(<MyComponent />);
    const form = component.find('input');
    // when
    form.props().onChange({target: {
      name: 'myName',
      value: 'myValue'
    }});
    // then
    expect(component.state('input')).toEqual('myValue');
  });
 ~~~

Not surprisingly, we get the following error.

~~
Expected value to equal: "myValue"
Received: ""

We finally make this test pass by getting the value from the event and setting it as the input value.

// MyComponent.js
import React from 'react';

export default class MyComponent extends React.Component {
  render() {
    return <div><input
      onChange={(event) => {
         this.setState({input: event.target.value})}}
      type="text" /></div>;
  }
}

After making sure all tests pass, we can refactor our code.

We can extract the function passed in the onChange prop to a new function called updateState.

// MyComponent.js
import React from 'react';

export default class MyComponent extends React.Component {
  updateState(event) {
    this.setState({
        input: event.target.value
    });
  }
  render() {
    return <div><input
      onChange={this.updateState.bind(this)}
      type="text" /></div>;
  }
}

We now have a simple React.js component created using TDD.

Summary

In this example, we tried to use pure TDD by following every step writing the least code possible to fail and pass tests.

Some of the steps may seem unnecessary and we may be tempted to skip them. However, whenever we skip any step, we’ll end up using a less pure version of TDD.

Using a less strict TDD process is also valid and may work just fine.

My recommendation for you is to avoid skipping any steps and don’t feel bad if you find it difficult. TDD is a technique not easy to master, but it is definitely worth doing.

If you’re interested in learning more about TDD and the related behavior-driven development (BDD), read Your Boss Won’t Appreciate TDD by fellow Toptaler Ryan Wilcox.

Understanding the basics

  • What is test-driven development?

    Test-driven development is a software development process based on the order in which we write test and production code. In a nutshell, we want to keep to a minimum both the test code necessary to fail and the production code necessary to pass.

  • What is a React component?

    A React component combines logic and presentational code. It’s mainly used to provide an abstract layer for web and mobile UI components based on state and properties changing

Hire a Toptal expert on this topic.
Hire Now
Alonso Ayala Ortega

Alonso Ayala Ortega

Verified Expert in Engineering

Málaga, Spain

Member since September 18, 2017

About the author

Over the last decade, Alonso’s Oracle certifications and full-stack work have lately turned toward QA automation and sharp BDD solutions.

Read More
authors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.

PREVIOUSLY AT

Netflix

World-class articles, delivered weekly.

Subscription implies consent to our privacy policy

World-class articles, delivered weekly.

Subscription implies consent to our privacy policy

Join the Toptal® community.