Testing React Apps with Jest and chai-enzyme for Beginners

“Testing is an infinite process of comparing the invisible to the ambiguous in order to avoid the unthinkable happening to the anonymous.” — James Bach

Introduction

In July, I completed the first project for the Udacity React Nanodegree Program. Having a QA background, I decided to write unit tests to ensure functionality didn’t break as I added new features.

After completing the project I decided to share it with my classmates via slack. Many of my classmates were impressed and I started receiving questions like:

I recommend a few tutorials, books and blog posts (see the Recommended Resources section).

Most of the resources I recommended, however, showed examples instead of explaining the thought process behind writing certain tests. In my opinion, the “why” is more important because that knowledge can be transferred onto testing other React apps.

It’s like the old saying — you give a man a few examples and he can only test a few scenarios. Tell a man the “why” and he can apply it to many other scenarios… or something like that 😶.

In this article, I assume you have some experience with React, JavaScript (ES6) and NPM. We’ll talk about:

  1. Testing Tools 🔨
  2. Folder Structure 🏗
  3. Thought Process 🤔
  4. Creating Tests 👨‍🔬
  5. Running the Tests 🏃
  6. Recommended Resources 📚

Testing Tools 🔨

The tools I use in the unit testing process:

  • Jest - A test runner (similar to mocha) that was built by Facebook to test JavaScript applications. I like Jest because it’s easy to setup, fast, has cool features like snapshot testing and it comes built in with code coverage and mocking.
  • chai-enzyme - Enzyme is a testing utility for React that allows developers to easily assert and manipulate components. Chai is an assertion library for node and the browser. chai-enzyme uses chai-like assertions to test React applications with enzyme.

I like to use chai-enzyme because it makes assertions more readable. For example:

// Using chai-enzyme 
expect(wrapper.find('#id')).to.be.present()
// Using enzyme
expect(wrapper.find('#id')).to.have.length(1)
  • Faker - A library that generates fake data.
faker.name.findName() // Usain Bolt

Folder Structure 🏗

Code is read more than it is written and so it is important to structure your project in such a way that allows files to be easily located.

I organize files based on feature instead of type. A feature-based folder structure allows for test files to be easily located.

Organization by Feature

/components
/Book
index.js // Component
index.spec.js // Test

Organization by Type

/components
/Book
index.js // Book Component
 /Tests 
book.spec.js // Test

Here’s a stackexchange answer explaining more about the pros and cons of the two folder structures.

Thought Process 🤔

Here are a few things I keep in mind when unit testing React apps:

Mocking Modules, Clearing Mocks and Fake Data

I usually start off my tests by creating mocks. That is, I mock functions / files that the component I’m testing uses. I mock functions using jest.fn() in the first describe method then clear the mock in the afterEach hook in that same describe method. This ensures that each test receives a freshly mocked function as they may be cases where subsequent tests/beforeEach functions modify the mocked method. Similarly, I create the fake data in the first describe to allow that data to be utilized in subsequent tests.

Initial State

I test the initial state to ensure that the component initialized the state correctly. This step is not necessarily needed because we can assume that the initial state that was specified will be rendered correctly by React.

However, it is good to have a test to verify that the initial state was initialized correctly before manipulating the state for other test scenarios. Ultimately, the granularity of your tests is a decision that you and your team would have to make before writing tests.

Events(e.g Button click)

I simulate events then assert that the state, props and the component change as expected. This simulation is done in the beforeEach hook and the assertions are carried out in the tests. Essentially, the beforeEach hook is used to setup scenarios to be tested.

One Assertion per Test

I try to limit each test to one assertion. If a test should fail, I can easily pick out the failed assertion.

Behavior Driven Testing (BDD)

This is an approach that is geared towards testing the behavior of the application instead of the implementation. BDD is a great way of unit testing React applications, since state and props change based on behavior. An example of BDD testing:

describe('Search', () => {

... // before hooks, setup mocks, generate fake data
  describe('when a user types in the search box', () => { 
... // tests
     describe('when the user clicks the search button', () => {
... // tests
       describe('when the user clears the search field', () => {
... // tests
})
)}
})
})

Creating Tests 👨‍🔬

The Application

I created a simple filterable product table (inspiration taken from the React docs) that filters a list of products as the user types in the search box. The application also has a “Only show products in stock” checkbox that filters the product list based on the products that are in stock.

The app is broken down into 5 components:

Image taken for the React docs
  • FilterableProductTable (orange): contains the entirety of the example
  • SearchBar(blue): receives all user input
  • ProductTable(green): displays and filters the data collection based on user input
  • ProductCategoryRow (turquoise): displays a heading for each category
  • ProductRow(red): displays a row for each product

Getting Started

To get started, clone or fork this repository, and checkout the start-template branch. This branch contains the React app without the tests.

Creating Test Data

The FilterableProductTable requires an array of products to be passed in as props. Let’s create some test data using faker that we can use in our tests.

Steps:

  1. npm install faker --save-dev
  2. Create a testData.js file in src/utils
  3. Create a generateProduct function to generate test data for a single product and a generateProductList function to generate a product list.
// testData.js
import faker from 'faker';
export const generateProduct = () => ({
category: faker.lorem.word(),
price: faker.commerce.price(),
stocked: faker.random.boolean(),
name: faker.commerce.productName(),
});
export const generateProductList = (count = 4) => {
const products = [];
for (let i = 0; i < count; i++) {
products.push(generateProduct());
}
return products;
}

View the commit.

Configure chai to use chai-enzyme

  1. npm install chai chai-enzyme --save-dev
  2. Create a chai.js file in src/utils .
import chai from 'chai';
import chaiEnzyme from 'chai-enzyme';
chai.use(chaiEnzyme());
export const expect = chai.expect;

We will import this file in our test to do assertions.

View the commit.

FilterableProductTable Tests

  1. npm install react-test-renderer enzyme --save-dev

2. Create a test file in the components folder called index.spec.js and import the dependencies.

// External Depedencies
import React from 'react';
import faker from 'faker'
import { shallow } from 'enzyme';
// Our Dependencies
import { expect } from '../../utils/chai';
import { generateProductList } from '../../utils/testData';
// Our Component
import FilterableProductTable from './index';

3. In the beforeEach hook we are going to shallow render our component. We will also generate a list of products using our generateProductList util function since our component need a list of products as props.

describe('FilterableProductTable', () => {
let wrapper;
const products = generateProductList();
  beforeEach(() => {
wrapper = shallow(
<FilterableProductTable
products={products}
/>
)
});

4. As mentioned in the Thought Process section above, my first tests usually verify that the state was correctly initialized.

it('should initialize the filterText state to empty string', () => {
expect(wrapper).to.have.state('filterText').to.equal('');
});
it('should initialize the inStockOnly state to false', () => {   
expect(wrapper).to.have.state('inStockOnly').to.equal(false);
})

View the commit.

5. The component has two functions, handleFilterTextInput and handleInStockInput that are responsible for updating the filterText and inStockOnly state respectively. To test these functions, I will use the wrapper.instance().${functionName} to invoke the functions then assert that the state changes accordingly.

it('should update the state filter to the correct value', () => {
const filterText = faker.lorem.word();
wrapper.instance().handleFilterTextInput(filterText);
expect(wrapper).to.have.state('filterText').to.equal(filterText);
});
it('should update the state inStockOnly to true', () => {
wrapper.instance().handleInStockInput(true);
expect(wrapper).to.have.state('inStockOnly').to.equal(true);
});

View the commit.

SearchBar Tests

Let’s move on to testing theSeachBarcomponent.

Importing dependencies:

// External Depedencies
import React from 'react';
import faker from 'faker';
import { shallow } from 'enzyme';
// Our Dependencies
import { expect } from '../../../utils/chai';
import { generateProductList } from '../../../utils/testData';
// Our Component
import SearchBar from './index';

Setting up our tests: We will mock our functions, clear our mocks and create fake data here.

describe('SearchBar', () => {  
let wrapper;

const onFilterTextInput = jest.fn(),
onInStockInput = jest.fn(),
filterText = faker.lorem.word();
    beforeEach(() => {    
wrapper = shallow(
<SearchBar
onFilterTextInput={onFilterTextInput}
onInStockInput={onInStockInput}
filterText={filterText}
inStockOnly={false}
/>
)
});
  afterEach(() => {    
onFilterTextInput.mockClear();
onInStockInput.mockClear();
});

For our tests, we can verify that the:

  • onChange functions are called the correct number of times.
describe(..., () => {
  beforeEach(() => {
value = faker.lorem.word();
const input = wrapper.find('input').first();

input.simulate('change', {
target: { value }
});
});
  it('should call onFilterTextInput once', () => {
expect(onFilterTextInput.mock.calls.length).to.equal(1);
});
})
  • onChange functions are called with the correct value
it('should call the onFilterTextInput with the right value', () => {
expect(onFilterTextInput.mock.calls[0][0]).to.equal(value);
});
  • Search input has the value of the filterText
it('should initialize search input to the filterText value', () => {
const input = wrapper.find('input').first();
expect(input).to.have.value(filterText);
});
  • Checkbox is checked whenever the inStockOnly prop is true
describe('...', () => {
  beforeEach(() => {
wrapper.setProps({
inStockOnly: true, // Set inStockOnly prop to true
});
});
it('should check the "Only show products in stock" checkbox',() => {
const checkbox = wrapper.find('p input').first();
expect(checkbox).to.be.checked();
});
});

It’s pretty much the same for theonInStockInput tests but instead of changing the target.value we’re going to change the target.checked value.

Instead of:

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

We’ll do:

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

View the commit for all the tests.

The ProductRow , ProductCategoryRow and ProductTable components also need to be tested. Feel free to submit a PR, I think this would be a fun exercise.

Running the Tests 🏃

Running npm test should get the tests running.

The test results should look something like this

Recommended Resources 📚

  • Lessons 22- 25 for 30 Days of React by FullStackReact #
  • FullStackReact book has an amazing testing chapter. I highly recommend this book! #
  • Good Practices for Testing React Apps by Tucker Connelly #
  • Unit Testing React Components: Jest or Enzyme? #
  • Testing React Applications with Jest #
  • Testing React Components with Enzyme and Jest video tutorial #

Conclusion

I hope you’re not afraid anymore when it comes to unit testing your React apps. Testing can be fun 🌝 and it can end up saving you lots of time as your app scales. I hope that you find this article useful or at least learnt something new.

I published on my Github account a more complex React project that contains more complex testing scenarios. You can find it here: https://github.com/romarioraffington/react-mybooks.

Feel free to add a response below or contact me directly if you have any questions or any suggestions on how to make this article better.


Hi, I’m Romario! I’m a Javascript application developer who has been programming for over 6 years. I ❤️ creating scalable, quality applications and writing articles about interesting technologies.