Хабрахабр

Модульное тестирование 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 ? <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);

В этом компоненте используется свойство 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( <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');
});

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

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

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

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

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

Теги
Показать больше

Похожие статьи

Кнопка «Наверх»
Закрыть