Unit testing react components withRouter (jest, enzyme)

When developing unit tests for a react component wrapped in a call withRouter (Component), I encountered an error message that such a component can exist only in the context of the router. The solution to this problem is very simple and should not in principle cause questions. Although for some reason, Google stubbornly refused to give links to documentation https://reacttraining.com/react-router/web/guides/testing . This does not surprise me at all, because the documentation is written as a pure SPA application without any SSR there and from the point of view of a search engine it looks like this:

Show image
image

Anyone with enough documentation can end up reading this. And for myself, I will make a few notes under the cut.

The tested component (paginator) takes as parameters the number of lines - total, on the page and the number of the current page. It is necessary to form a component with links of the form:

  • / or / my / base / url - for the first page
  • / page / {n} or / my / base / url / page / {n} - for other pages
  • for single-page documents, do not form a component

import React from 'react';
import _ from 'lodash';
import { withRouter } from 'react-router-dom';
import Link from '../asyncLink'; // eslint-disable-line

function prepareLink(match, page) {
  const { url } = match;
  const basePath = url.replace(/\/(page\/[0-9]+)?$/, '');
  if (page === 1) {
    return basePath || '/';
  }
  return `${basePath}/page/${page}`;
}

const Pagination = ({ count, pageLength, page, match }) => ( // eslint-disable-line react/prop-types, max-len
  count && pageLength && count > pageLength
    ?
      <nav>
        <ul className="pagination">
          {
            _.range(1, 1 + Math.ceil(count / pageLength)).map(index => (
              <li className={`page-item${index === page ? ' active' : ''}`} key={index}>
                <Link className="page-link" to={prepareLink(match, index)}>
                  {index}
                </Link>
              </li>))
          }
        </ul>
      </nav>
    : null
);

export default withRouter(Pagination);

This component uses the match property, which is only available for components wrapped in a call withRouter(Pagination) . When testing, you need to create a context using a special router for testing - MemoryRouter. And also place the component in the appropriate Route route to form matches:

/* eslint-disable no-undef, function-paren-newline */
import React from 'react';
import { MemoryRouter, Route } from 'react-router-dom';
import { configure, mount } from 'enzyme';
import renderer from 'react-test-renderer';
import Adapter from 'enzyme-adapter-react-16';
import Pagination from '../../../src/react/components/pagination';

configure({ adapter: new Adapter() });

test('Paginator snapshot', () => {
  const props = {
    count: 101,
    pageLength: 10,
    page: 10,
  };
  const component = renderer.create(
    <MemoryRouter initialEntries={['/', '/page/10', '/next']} initialIndex={1}>
      <Route path="/page/:page">
        <Pagination {...props} />
      </Route>
    </MemoryRouter>,
  );
  const tree = component.toJSON();
  expect(tree).toMatchSnapshot();
});

test('Paginator for root route 1st page', () => {
  const props = {
    count: 101,
    pageLength: 10,
    page: 1,
  };
  const component = mount(
    <MemoryRouter initialEntries={['/before', '/', '/next']} initialIndex={1}>
      <Route path="/">
        <Pagination {...props} />
      </Route>
    </MemoryRouter>,
  );
  expect(component.find('li.active').find('a').prop('href')).toEqual('/');
  expect(component.find('li').first().find('a').prop('href')).toEqual('/');
});

test('Paginator for root route 2nd page', () => {
  const props = {
    count: 101,
    pageLength: 10,
    page: 2,
  };
  const component = mount(
    <MemoryRouter initialEntries={['/', '/page/2', '/next']} initialIndex={1}>
      <Route path="/page/:page">
        <Pagination {...props} />
      </Route>
    </MemoryRouter>,
  );
  expect(component.find('li.active').find('a').prop('href')).toEqual('/page/2');
  expect(component.find('li').first().find('a').prop('href')).toEqual('/');
});


test('Paginator for some route 1st page', () => {
  const props = {
    count: 101,
    pageLength: 10,
    page: 1,
  };
  const component = mount(
    <MemoryRouter initialEntries={['/', '/some', '/next']} initialIndex={1} context={{}}>
      <Route path="/some">
        <Pagination {...props} />
      </Route>
    </MemoryRouter>,
  );
  expect(component.find('li.active').find('a').prop('href')).toEqual('/some');
  expect(component.find('li').first().find('a').prop('href')).toEqual('/some');
});

test('Paginator for /some route 2nd page', () => {
  const props = {
    count: 101,
    pageLength: 10,
    page: 2,
  };
  const component = mount(
    <MemoryRouter initialEntries={['/', '/some/page/2', '/next']} initialIndex={1}>
      <Route path="/some/page/:page">
        <Pagination {...props} />
      </Route>
    </MemoryRouter>,
  );
  expect(component.find('li.active').find('a').prop('href')).toEqual('/some/page/2');
  expect(component.find('li').first().find('a').prop('href')).toEqual('/some');
});

The MemoryRouter contains an “imaginary” history of the component, along which you can then move back and forth. The starting index is set by the property initialIndex .

Tests use a popular library from airbnb - enzyme and are run by the jest command.
The jest framework at the first start forms a snapshot of the component, with which it then compares the document resulting from the rendering:

  const tree = component.toJSON();
  expect(tree).toMatchSnapshot();

The enzyme library commands for searching for and analyzing DOM elements do not look as concise as jquery for example. Nevertheless, everything is very convenient.

apapacy@gmail.com
March 5, 2018.