Skip to the content.

Back

Testing

Create React App uses Jest as its test runner.

This is the Create React App Testing documentation.

Snapshot Testing is a feature of Jest that automatically generates text snapshots of your components and saves them on the disk so if the UI output changes, you get notified without manually writing any assertions on the component output. Read more about snapshot testing.

When you run npm test, Jest will launch in watch mode. Every time you save a file, it will re-run the tests, like how npm start recompiles the code.You can disable this behavior by passing in the –watchAll=false flag.

Jest has an integrated coverage reporter that works well with ES6 and requires no configuration.

Testing with React/Redux

Because most of the Redux code you write are functions, and many of them are pure, they are easy to test without mocking.

Testing with Hooks

Custom Hooks

react-hooks-testing-library is a very used React hooks testing utilities.

Installation and Code samples

Function component

react-hooks-testing-library is useful for testing custom hooks, in order to testing hooks inside a function component, a useful approach would be test the side effects simulating events like clicks.

Example:

it('should set the password value on change event with trim', () => {
    container.find('input[type="password"]').simulate('change', {
      target: {
        value: 'somenewpassword  ',
      },
    });
    expect(container.find('input[type="password"]').prop('value')).toEqual(
      'somenewpassword',
    );
  });

An alternative to simulating events using simulate method is to execute the props by calling them as functions by passing in the necessary params. It is useful when we have a custom component with custom methods as props, in these cases the simulate method wouldn’t work.

container.find('input[type="password"]').prop('onChange')({
  target: {
    value: 'somenewpassword',
  }
});

Alternatively, we could also mock the Hooks using Jest.

Lifecycle hooks

Lifecycle hooks such as useEffect aren’t yet supported in shallow render (those hooks don’t get called) so we need to use mount instead of shallow to test those components for now. Like with the useState hook we check for updates to props to test these hooks by simulating events or executing props as functions.

Methods that don’t update state

The methods that don’t manipulate the state can be refactored out of the component into a separate utils file and tested in it instead of having them inside the component. If the methods are pretty specific to the component and aren’t shared outside the component we could have it inside the component file but outside the main function component.

Hooks from Redux

There are 2 kinds of hooks you will encounter.

And the essential concepts are: the first one is a unit test method, the 2nd solution is an integration test.

How to test separated custom hooks

Let’s say you have a custom hook function:

import { useSelector, useDispatch } from "react-redux";
import { Selectors } from "./selectors";
import { Actions } from "./actions";

export const useReset = () => {
  const totalCost = useSelector(Selectors.totalCost);
  const dispatch = useDispatch();

  return () => {
    if (totalCost > 0) {
      dispatch(Actions.reset());
    }
  };
};

It is simple, we will dispatch Actions.reset() when totalCost is greater than 0.

For the testing this, you can simply monkeypatch all the methods that you are using here in terms of changing the behavior when testing.

import { useReset } from "./useReset";
import { Selectors } from "./selectors";
import { Actions } from "./actions";

jest.mock("react-redux", () => ({
  useSelector: jest.fn(fn => fn()),
  useDispatch: () => jest.fn()
}));

const setup = ({ totalCost }) => {
  jest.spyOn(Selectors, "totalCost").mockReturnValue(totalCost);
  jest.spyOn(Actions, "reset");
};

describe("useReset", () => {
  afterEach(() => {
    jest.clearAllMocks();
  });

  afterAll(() => {
    jest.restoreAllMocks();
  });

  test("Success Case", () => {
    setup({ totalCost: 1 });

    const resetFunc = useReset();
    resetFunc();

    expect(Actions.reset).toHaveBeenCalledTimes(1);
  });

  test("Failure Case", () => {
    setup({ totalCost: 0 });

    const resetFunc = useReset();
    resetFunc();

    expect(Actions.reset).toHaveBeenCalledTimes(0);
  });
});
How to test component with hooks inside

In this case, you can simply create a fake store from the lib redux-mock-store, and use it to wrap your component, so every test is more like an integration test involved not only the Components but the selectors as well.

redux-mock-store

import React from 'react';
import { mount } from 'enzyme';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-redux';

import Header from '../../components/Header';
import { addTodo } from '../../store/actions/todos';

const middlewares = [];
const mockStore = configureStore(middlewares);
const initialState = { };
const store = mockStore(initialState);
const wrapper = mount(
  <Provider store={store}>
    <Header />
  </Provider>,
);

describe('Header component', () => {
  it('should render without crashing', () => {
    expect(wrapper).toMatchSnapshot();
  });

  it('should change the state', () => {
    const input = 'Input example';
    wrapper.find('[id="textInput"]').first().simulate('change', { target: { value: input } });
    expect(wrapper.find('[id="textInput"]').first().prop('value')).toEqual(input);
  });

  it('should add a new todo', () => {
    const input = 'Input example';
    wrapper.find('[id="textInput"]').first().simulate('change', { target: { value: input } });
    wrapper.find('[id="addForm"]').first().simulate('submit', {
      preventDefault: jest.fn(),
      target: { value: input },
    });
    const actions = store.getActions();
    const text = input.trim();
    const expectedPayload = addTodo(text);
    expect(actions).toEqual([expectedPayload]);
    store.clearActions();
    expect(wrapper.find('[id="textInput"]').first().prop('value')).toEqual('');
  });
});

References