Testing React Apps
В Facebook мы используем Jest для тестирования React приложений.
Настройка #
Настройка с Create React App #
Если вы только знакомитесь с React, мы рекомендуем использовать Create React App. Готово к использованию и поставляется вместе с Jest! Вам не нужно совершать какие-либо дополнительные шаги для установки и вы уже можете направиться к следующему разделу.
Настройка без Create React App #
Если у вас есть существующее приложение, то вам понадобится установить несколько пакетов, чтобы все хорошо работало в совместности. Мы используем пакет babel-jest
и Babel прежде для react
, чтобы преобразовать код внутри окружения для тестирования. Также см. использование Babel.
Запуск
npm install --save-dev jest babel-jest babel-preset-es2015 babel-preset-react react-test-renderer
Ваш package.json
должен выглядеть примерно так (где <current-version>
это фактический номер последней версии зависимости). Пожалуйста, добавьте скрипты и конфигурационные записи для Jest:
// package.json "dependencies": { "react": "<current-version>", "react-dom": "<current-version>" }, "devDependencies": { "babel-jest": "<current-version>", "babel-preset-es2015": "<current-version>", "babel-preset-react": "<current-version>", "jest": "<current-version>", "react-test-renderer": "<current-version>" }, "scripts": { "test": "jest" }
// .babelrc { "presets": ["es2015", "react"] }
И все готово!
Тестирование при помощи снимков #
Создадим тест использующий снимок для компонента Link, который отображает гиперссылки:
// Link.react.js import React from 'react'; const STATUS = { HOVERED: 'hovered', NORMAL: 'normal', }; export default class Link extends React.Component { constructor(props) { super(props); this._onMouseEnter = this._onMouseEnter.bind(this); this._onMouseLeave = this._onMouseLeave.bind(this); this.state = { class: STATUS.NORMAL, }; } _onMouseEnter() { this.setState({class: STATUS.HOVERED}); } _onMouseLeave() { this.setState({class: STATUS.NORMAL}); } render() { return ( <a className={this.state.class} href={this.props.page || '#'} onMouseEnter={this._onMouseEnter} onMouseLeave={this._onMouseLeave}> {this.props.children} </a> ); } }
Теперь используем рендерер тестов React и функции создания снимков Jest для взаимодействия с компонентом и захвата результата его отображения, а также создания файла снимка:
// Link.react-test.js import React from 'react'; import Link from '../Link.react'; import renderer from 'react-test-renderer'; test('Link changes the class when hovered', () => { const component = renderer.create( <Link page="http://www.facebook.com">Facebook</Link> ); let tree = component.toJSON(); expect(tree).toMatchSnapshot(); // manually trigger the callback tree.props.onMouseEnter(); // re-rendering tree = component.toJSON(); expect(tree).toMatchSnapshot(); // manually trigger the callback tree.props.onMouseLeave(); // re-rendering tree = component.toJSON(); expect(tree).toMatchSnapshot(); });
При выполнении команды npm test
или jest
, будет выведен похожий результат:
// __tests__/__snapshots__/Link.react-test.js.snap exports[`Link changes the class when hovered 1`] = ` <a className="normal" href="http://www.facebook.com" onMouseEnter={[Function]} onMouseLeave={[Function]}> Facebook </a> `; exports[`Link changes the class when hovered 2`] = ` <a className="hovered" href="http://www.facebook.com" onMouseEnter={[Function]} onMouseLeave={[Function]}> Facebook </a> `; exports[`Link changes the class when hovered 3`] = ` <a className="normal" href="http://www.facebook.com" onMouseEnter={[Function]} onMouseLeave={[Function]}> Facebook </a> `;
При следующем запуске тестов, отображаемый вывод будет сравнен с сохраненным снимком. Этот снимок следует занести в систему контроля версий наряду с изменениями в коде. Когда тест использующий снимки проваливается, вам следует проверить предвиденные это изменения или нет. Если изменения предвиденные, то вы можете запустить Jest командой jest -u
для перезаписи существующих снимков.
Код для этого примера доступен в examples/snapshot.
Тестирование DOM #
Если возникает необходимость создать утверждения и манипулировать вашими отображаемыми компонентами, то вы можете использовать Enzyme или React TestUtils. Для этого примера мы будем использовать Enzyme.
Вам необходимо выполнить команду npm install --save-dev enzyme
для использования Enzyme. Если вами используется React версии ниже 15.5.0, то вам также потребуется установить react-addons-test-utils
.
Создадим простой чекбокс, который переключается между двумя лейблами:
// CheckboxWithLabel.js import React from 'react'; export default class CheckboxWithLabel extends React.Component { constructor(props) { super(props); this.state = {isChecked: false}; // bind manually because React class components don't auto-bind // http://facebook.github.io/react/blog/2015/01/27/react-v0.13.0-beta-1.html#autobinding this.onChange = this.onChange.bind(this); } onChange() { this.setState({isChecked: !this.state.isChecked}); } render() { return ( <label> <input type="checkbox" checked={this.state.isChecked} onChange={this.onChange} /> {this.state.isChecked ? this.props.labelOn : this.props.labelOff} </label> ); } }
В этом примере мы используем в shallow renderer включенный в Enzyme.
// __tests__/CheckboxWithLabel-test.js import React from 'react'; import {shallow} from 'enzyme'; import CheckboxWithLabel from '../CheckboxWithLabel'; test('CheckboxWithLabel changes the text after click', () => { // Render a checkbox with label in the document const checkbox = shallow( <CheckboxWithLabel labelOn="On" labelOff="Off" /> ); expect(checkbox.text()).toEqual('Off'); checkbox.find('input').simulate('change'); expect(checkbox.text()).toEqual('On'); });
Код для этого примера доступен в examples/enzyme.
Пользовательские трансформаторы #
Если требуется реализовать более продвинутый функционал, то можно создать ваш собственный трансформатор. Вместо использования babel-jest, вот пример использования babel:
// custom-transformer.js 'use strict'; const babel = require('babel-core'); const jestPreset = require('babel-preset-jest'); module.exports = { process(src, filename) { if (babel.util.canCompile(filename)) { return babel.transform(src, { filename, presets: [jestPreset], retainLines: true, }).code; } return src; }, };
Не забудьте установить пакеты babel-core
и babel-preset-jest
, чтобы данный пример сработал.
Для использования с Jest, вам нужно будет добавить следующий код в вашу Jest конфигурацию: "transform": {"\\.js$": "path/to/custom-transformer.js"}
.
Если потребуется создать трансформатор в поддержкой babel, то также можно использовать babel-jest для его композиции и передачи пользовательской конфигурации:
const babelJest = require('babel-jest'); module.exports = babelJest.createTransformer({ presets: ['my-custom-preset'], });