Testing React Apps
在 Facebook,我们使用 Jest 测试 React 应用。
安装 #
使用Create React App #
如果你是刚开始学习React,我们推荐使用Create React App. 它现成就有,直接就用 你不需要进行其他额外的安装操作,可直接看下一节内容。
不使用Create React App #
如果你已经有一个应用,你仅需要安装一些包来使他们运行起来。 我们使用babel-jest
包和babel-preset-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的test renderer和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 -u
调用Jest从而重写存在的快照。
该例的代码在examples/snapshot
DOM测试 #
如果你想要断言和操纵您渲染的组件你可以使用 Enzyme 或 React 的 TestUtils。本例中我们使用 Enzyme。 本例中我们使用 Enzyme。
要使用 enzyme 你需要运行 npm install --save-dev 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> ); } }
在这个例子中我们使用了 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'], });