Модульное тестирование react компонетнов withRouter (jest, enzyme)

При разработке модульных тестов для react компонента, обернутого в вызов withRouter (Component) столкнулся с сообщением об ошибке, что такой компонент может существовать только в контексте роутера. Решение этой проблемы очень простое и не должно по идее вызывать вопрсов. Хотя почему-то ссылки на документацию https://reacttraining.com/react-router/web/guides/testing Google упорно отказывался выдавать. Меня это совсем не удивляет, т.к. документация написано как чистое SPA-приложение без всякого там SSR и с точки зрения поисковой машины выглядит вот так:

Показать изображение
image


Кому достаточно документации может на этом закончить чтение. А для себя я сделаю несколько заметок под катом.

Тестируемый компонент (paginator) принимает в качестве параметров количество строк — всего, на странице и номер текущей страницы. Необходимо сформировать компонент со ссылками вида:

  • / или /my/base/url — для первой страницы
  • /page/{n} или /my/base/url/page/{n} — для остальных страниц
  • для одностраничных документов компонент не формировать


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
    ?
      
    : null
);

export default withRouter(Pagination);


В этом компоненте используется свойство match, которое становится доступным только для компонентов, обернутых в вызов withRouter(Pagination). При тестировании нужно создать контекст при помощи специального роутера для тестирвлоани — MemoryRouter. А так же поместить компонент в соотвтествующий роут Route для формирования 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(
    
      
        
      
    ,
  );
  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(
    
      
        
      
    ,
  );
  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(
    
      
        
      
    ,
  );
  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(
    
      
        
      
    ,
  );
  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(
    
      
        
      
    ,
  );
  expect(component.find('li.active').find('a').prop('href')).toEqual('/some/page/2');
  expect(component.find('li').first().find('a').prop('href')).toEqual('/some');
});


MemoryRouter содержит «воображаемую» историю компонента, по которой можно будет потом двигаться вперед и назад. Начальный индекс задается свойством initialIndex.

Тесты используют популяртную библиотеку от airbnb — enzyme и запусткаются командой jest.
Фреймверк jest при первом запуске формирует моментальный снимок компонента, с которым потом сверяет получаемый в результате рентеринга документ:

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


Команды библиотеки enzyme для поиска элементов DOM и их анализа выглядят не так лаконично как например jquery. Тем не менее все очень удобно.

apapacy@gmail.com
5 марта 2018 года.

© Habrahabr.ru