Use Jest for React App Testing

Written by: David Roman

By Dmitry Shishko, Full Stack Developer.


In this article, I would like to share my experience with testing the React application. There are quite a few tools for testing applications, such as MochaJasmineJestKarmaChai and many more.

What I plan on covering is:

  • Component testing
  • User behavior testing

For the purposes of this article, I would like to investigate the use of Jest from Facebook, because it’s pretty simple to configure and use.

What Jest allows:

  1. Quick installation and configuration
  2. Running tests before deployment or in development mode
  3. Unit test, functional tests and snapshot tests
  4. Code coverage reports integrate simply with sonarqube
  5. Mock data for test

Together with Jest, I use Enzyme. Enzyme is a tool that makes it easy to manipulate DOM elements.

Installing Jest

What we install:

  • Jest — Jest is used by Facebook to test all JavaScript code including React applications.
  • Babel-jest — setup to use all ES6 features and React specific syntax.
  • Enzyme — Enzyme is a JavaScript testing utility for React that makes it easier to assert, manipulate, and traverse your React Components’ output.
  • Enzyme-to-json — convert the Enzyme wrapper to format compatibility with Jest.
  • Nock — allows you to mock objects in your test files.
  • Redux-mock-store — used to mock our Redux store.

Then set up your package.json

Running the test is done using the command “npm run test” or “npm run test:watch”. Results will look like this:

Testing with Snapshot:

A handy tool that takes a snapshot of your component, and then checks the UI for changes. Below is an example of testing a simple “List” component:

List.js

ListRow.js

./__tests__/List.spec.js

Jest automatically generates a snapshot.

./__tests__/__snaphots__/List.spec.js.snap

As you can see, everything is quite simple.

Testing connected components.

I have a Login Page.js component. In this component I have structured the page to include a header, fields for email and password, and the login button. File structure like this:

Сreate the file ./__tests/LoginPage.spec.js

import React from 'react';
import { shallow } from 'enzyme';
import toJson from 'enzyme-to-json';
import thunk from 'redux-thunk';
import configureStore from 'redux-mock-store';
import ConnectedLogin, { LoginPage } from '../LoginPage';
import LogoHeader from '../LogoHeader';
// Props, which we pass to component
const props = {
 localization: {
  common: {
   login: 'Login',
  },
  form: {
   email: 'Email',
   password: 'Password',
   emailRequired: 'Email is required',
   emailNotValid: 'Email is not valid',
   passwordRequired: 'Password is required',
  }
 },
 currentLang: 'en',
 login: () => {},
};
const middlewares = [thunk];
const mockStore = configureStore(middlewares);
describe('>>>LoginPage - Shallow Render REACT COMPONENTS', () => {
 let wrapper;
beforeEach(() => {
  wrapper = itmes => shallow(<LoginPage {…itmes} />);
 });
// Create 'snapshot'
 it('+++ render LoginPage correctly, snapshot', () => {
  const component = wrapper(props);
  expect(toJson(component)).toMatchSnapshot();
 });
// Check render comonent without errors
 it('+++ render the DUMB component', () => {
  expect(wrapper(props).length).toEqual(1);
 });
// Check the existence of header component
 it('+++ contains LogoHeader component', () => {
  expect(wrapper(props).find(LogoHeader).length).toBe(1);
});
// Simple behavior for fill in the fields
 it('+++ simulate change email, password input', () => {
  const component = wrapper(props);
  const eventEmail = {
   target: { name: 'email', value: 'test@test.com' }
  };
  const eventPassword = {
   target: { name: 'password', value: '123456' }
  };
  component.find('[name="email"]').simulate('change', eventEmail);
  component.find('[name="password"]')
   .simulate('change', eventPassword);
expect(component.state('email')).toEqual('test@test.com');
  expect(component.state('password')).toEqual('123456');
  expect(component.find('[name="email"]').prop('value'))
   .toEqual('test@test.com');
  expect(component.find('[name="password"]').prop('value'))
   .toEqual('123456');
 });
 
 it('+++ triggers submit handler with valid form data', () => {
  const mockFn = jest.fn(() => Promise.resolve({}));
  props.login = mockFn;
  const component = wrapper(props)
    .setState({ email: 'test@test.com', password: '123456' });
  component.find('button[type="button"]').simulate('click');

  expect(mockFn).toHaveBeenCalledWith({ 
    email: 'test@test.com', password: '123456'
  });
  expect(mockFn).toHaveBeenCalledTimes(1);
 });
});
// setup for testing connected component
const mockStore = configureStore(middlewares);
const localization = {
 data: {
  localization: {
   common: {
    login: 'Login',
   },
   form: {
    email: 'Email',
    password: 'Password',
    emailRequired: 'Email is required',
    emailNotValid: 'Email is not valid',
    passwordRequired: 'Password is required',
   },
  },
  type: 'en',
 },
};
const auth = {
 current: {},
};
const initialState = {
 localization,
 auth,
};
describe('>>>LoginPage - REACT-REDUX (Shallow + passing the {store} directly)’, () => {
 let container;
 beforeEach(() => {
  const store = mockStore(initialState);
  container = shallow(<ConnectedLoginPage store={store} />);
 });
it('+++ render the connected(SMART) component', () => {
  expect(container.length).toEqual(1);
 });

 it('+++ check Prop matches with initialState', () => {
  expect(container.prop('currentLang')).toEqual('en');
 });
});
describe('>>>LoginPage — REACT-REDUX (Mount + wrapping in Provider)', () => {
 let container;
 beforeEach(() => {
  const store = mockStore(initialState);
  container = mount(
   <Provider store={store}><ConnectedLoginPage /></Provider>
  );
 });
it('+++ render the connected(SMART) component', () => {
  expect(container.find(ConnectedLoginPage).length).toEqual(1);
 });
it('+++ contains class component', () => {
  expect(container.find('.grayBackground').length).toBe(1);
 });

 it('+++ check Prop matches with initialState', () => {
  expect(container.find(LoginPage).prop('currentLang'))
   .toEqual(initialState.localization.data.type);
 });
});

Test coverage

For code coverage, you only need to run the command "npm run test:coverage".

Conclusion

As you can see, it’s not particularly difficult to cover components with testing. I really hope this article will help you improve your coding.

Thanks for reading!

(Visited 2,049 times, 1 visits today)
David Roman
Author info
David Roman
David is our content & social media specialist. He helps with the planning, creation, and distribution of all the social content at WeAreBrain. He also loves green tea, listening to good music and using his analogue camera.
Close