Recently, I was assigned a couple of tasks to write unit test (UT) for a frontend project written in React/Redux, which is not really my expertise. This note is for future reference (hopefully not) about how to setup and write basic UT for React component.

Libraries used:

  • Jest: a JavaScript unit testing framework, used by Facebook to test services and React applications. Jest acts as a test runner, assertion library, and mocking library.
  • Enzyme: a JavaScript Testing utility for React that makes it easier to assert, manipulate, and traverse your React Components’ output. Enzyme can be used without Jest, however Enzyme must be paired with another test runner if Jest is not used.

Setup:

npm install --save-dev jest
npm install --save-dev enzyme enzyme-adapter-react-16 enzyme-to-json

You will need to install enzyme along with an Adapter corresponding to the version of react. I am using React 16.8.6 so I will use enzyme-adapter-react-16

setup the adapter by create a setupTests.js file at project-name/setupTests.js:

import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({ adapter: new Adapter() });

Config Jest in package.json file:

"jest": {
    "verbose": true,
    "moduleNameMapper": {
      "^~(.*)$": "<rootDir>/src$1",
      "\\.(jpg|gif|png)$": "<rootDir>/src/__mocks__/fileMock.js",
      "\\.(css)$": "<rootDir>/node_modules/jest-css-modules"
    },
    "moduleDirectories": [
      "node_modules",
      "src"
    ],
    "setupFiles": [
      "./setupJest.js"
    ]
  }

moduleNameMapper: A map from regular expressions to module names that allow to stub out resources. In my app,

  • I use alias import ~/.../... (i.e. import endpoints from '~/constant/BackendEndpoints'; so I need to map ~ to src folder.
  • When you import image files/ css files, Jest tries to interpret the binary codes of the images as .js, hence runs into errors, so we also need a map for that also.
  • <rootDir> is the root of the directory containing package.json
  • check other config here

Write UT

Snapshot test:
  • A typical snapshot test case renders a UI component, takes a snapshot, then compares it to a reference snapshot file stored alongside the test. The test will fail if the two snapshots do not match: either the change is unexpected, or the reference snapshot needs to be updated to the new version of the UI component.
import React from 'react';
import { shallow } from 'enzyme';
import HelloWorld from '~/components/HelloWorld';

beforeEach(() => {
    shallowWrapper = shallow(<HelloWorld />);
});

describe('Test HelloWorld component', () => {
    it('HelloWorld should match snapshot', () => {
        expect(toJson(shallowWrapper))
            .toMatchSnapshot();
    });
});

we can pass props to shallowWrapper:

shallowWrapper = shallow(<HelloWorld name="Quang" />);
Find a sub-component
import { Button } from 'react-bootstrap';
const button = shallowWrapper.find('.btn');
const button = shallowWrapper.find('#btn-info');
const button = shallowWrapper.find(Button);
const textarea = shallowWrapper.findWhere(n => n.prop('type') === 'textarea' && n.prop('id') === 'conversionResult')

simulate the action on sub-component: here we simulate click action on a button with parameter:

submit.simulate('click', { params: "param value" });

Get state, prop of Component:
  • component under test:
shallowWrapper.state().name
expect(shallowWrapper.state().loading).toBe(true);
  • sub-component:
shallowWrapper.find(SubComponent).prop('name')
shallowWrapper.find(Modal).prop('onChange')({ target: { value: "1" }})
expect(shallowWrapper.find(Modal).prop('show')).toEqual(false);
Call a function of Component:
shallowWrapper.instance().doSomthing();
shallowWrapper.update();

remember to call update() if you want props changed reflected after function is called.

Mock a API call by Axios

we need to install axios-mock-adapter first

import MockAdapter from 'axios-mock-adapter';
const mock = new MockAdapter(axios);
mock.onGet('api.example.com/users')
            .reply(200, {
                data: 'Fake response',
            });
            
mock.onPost('api.example.com/users').networkError();