在Facebook,我們使用 Jest 測(cè)試 React 應(yīng)用程序。
如果你是 React 新手,我們建議使用 ?Create React App
?。 它已經(jīng)包含了可用的 Jest! 你只需要添加 ?react-test-renderer
? 來(lái)渲染快照。
運(yùn)行
yarn add --dev react-test-renderer
如果你已經(jīng)有一個(gè)應(yīng)用,你僅需要安裝一些包來(lái)使他們運(yùn)行起來(lái)。 我們使用?babel-jest
?包和?babel-preset-react
?,從而在測(cè)試環(huán)境中轉(zhuǎn)換我們代碼。 可參考使用babel
運(yùn)行
yarn add --dev jest babel-jest @babel/preset-env @babel/preset-react react-test-renderer
你的?package.json
?文件應(yīng)該像下面這樣(?<current-version>
?是當(dāng)前包的最新版本號(hào)) 請(qǐng)?zhí)砑幽_本項(xiàng)目和 jest 配置:
// package.json
"dependencies": {
"react": "<current-version>",
"react-dom": "<current-version>"
},
"devDependencies": {
"@babel/preset-env": "<current-version>",
"@babel/preset-react": "<current-version>",
"babel-jest": "<current-version>",
"jest": "<current-version>",
"react-test-renderer": "<current-version>"
},
"scripts": {
"test": "jest"
}
// babel.config.js
module.exports = {
presets: ['@babel/preset-env', '@babel/preset-react'],
};
準(zhǔn)備工作已經(jīng)完成!
讓我們來(lái)為一個(gè)渲染超鏈接的 Link 組件創(chuàng)建快照測(cè)試
// 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>
);
}
}
現(xiàn)在,使用React的test renderer和Jest的快照特性來(lái)和組件交互,獲得渲染結(jié)果和生成快照文件:
// Link.react.test.js
import React from 'react';
import renderer from 'react-test-renderer';
import Link from '../Link.react';
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();
});
當(dāng)你運(yùn)行 ?npm test
? 或者 ?jest
?,將產(chǎn)生一個(gè)像下面的文件:
// __tests__/__snapshots__/Link.react.test.js.snap
exports[`Link changes the class when hovered 1`] = `
<a
className="normal"
rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank"
onMouseEnter={[Function]}
onMouseLeave={[Function]}>
</a>
`;
exports[`Link changes the class when hovered 2`] = `
<a
className="hovered"
rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank"
onMouseEnter={[Function]}
onMouseLeave={[Function]}>
</a>
`;
exports[`Link changes the class when hovered 3`] = `
<a
className="normal"
rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank"
onMouseEnter={[Function]}
onMouseLeave={[Function]}>
</a>
`;
下次你運(yùn)行測(cè)試時(shí),渲染的結(jié)果將會(huì)和之前創(chuàng)建的快照進(jìn)行比較??煺諔?yīng)與代碼更改一起提交。當(dāng)快照測(cè)試失敗,你需要去檢查是否是你想要或不想要的變動(dòng)。 如果變動(dòng)符合預(yù)期,你可以通過(guò)?jest -u
?調(diào)用Jest從而重寫(xiě)存在的快照。
該示例代碼在 examples/snapshot
在使用 Enzyme 和 React 16+ 時(shí),有一個(gè)關(guān)于快照測(cè)試的警告。如果你使用以下樣式模擬模塊:
jest.mock('../SomeDirectory/SomeComponent', () => 'SomeComponent');
然后你會(huì)在控制臺(tái)看到警告:
Warning: <SomeComponent /> is using uppercase HTML. Always use lowercase HTML tags in React.
# Or:
Warning: The tag <SomeComponent> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.
React 16 由于檢查元素類型的方式而觸發(fā)這些警告,并且模擬模塊未通過(guò)這些檢查。你的選擇是:
jest.mock('./SomeComponent',()=>()=>'SomeComponent');
jest.mock('./Widget',()=>()=><mock-widget />);
jest.mock('fbjs/lib/warning',()=>require('fbjs/lib/emptyFunction'));
這通常不應(yīng)該是你的選擇,因?yàn)橛杏玫木婵赡軙?huì)丟失。然而,在某些情況下,例如在測(cè)試 react-native 的組件時(shí),我們將 react-native 標(biāo)簽渲染到 DOM 中,許多警告是無(wú)關(guān)緊要的。另一種選擇是調(diào)整 console.warn 并抑制特定警告。
如果你想斷言和操作你的渲染組件,你可以使用react-testing-library、Enzyme或 React 的TestUtils。以下兩個(gè)示例使用 react-testing-library 和 Enzyme。
你必須運(yùn)行?yarn add --dev @testing-library/react
?才能使用 react-testing-library。
讓我們實(shí)現(xiàn)一個(gè)在兩個(gè)標(biāo)簽之間交換的復(fù)選框:
// 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
// https://reactjs.org/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>
);
}
}
// __tests__/CheckboxWithLabel-test.js
import React from 'react';
import {cleanup, fireEvent, render} from '@testing-library/react';
import CheckboxWithLabel from '../CheckboxWithLabel';
// Note: running cleanup afterEach is done automatically for you in @testing-library/react@9.0.0 or higher
// unmount and cleanup DOM after the test is finished.
afterEach(cleanup);
it('CheckboxWithLabel changes the text after click', () => {
const {queryByLabelText, getByLabelText} = render(
<CheckboxWithLabel labelOn="On" labelOff="Off" />,
);
expect(queryByLabelText(/off/i)).toBeTruthy();
fireEvent.click(getByLabelText(/off/i));
expect(queryByLabelText(/on/i)).toBeTruthy();
});
這個(gè)例子的代碼可以在examples/react-testing-library 找到。
你必須運(yùn)行?yarn add --dev enzyme
?才能使用 Enzyme。如果你使用的是低于 15.5.0 的 React 版本,你還需要安裝?react-addons-test-utils
?.
讓我們用Enzyme而不是反應(yīng)測(cè)試庫(kù)從上面重寫(xiě)測(cè)試。在這個(gè)例子中我們使用了 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 獲得。
如果你需要更高級(jí)的功能,還可以構(gòu)建自己的變壓器。這里沒(méi)有使用 babel-jest,而是使用 babel 的一個(gè)例子:
// custom-transformer.js
'use strict';
const {transform} = require('@babel/core');
const jestPreset = require('babel-preset-jest');
module.exports = {
process(src, filename) {
const result = transform(src, {
filename,
presets: [jestPreset],
});
return result ? result.code : src;
},
};
不要忘記安裝@?babel/core
?和?babel-preset-jest
?包以使本示例正常工作。
為了使這個(gè)與 Jest 一起工作,你需要更新你的 Jest 配置:?"transform": {"\\.js$": "path/to/custom-transformer.js"}
?。
如果你想建立一個(gè)帶 babel 支持的轉(zhuǎn)譯器,你還可以使用 babel-jest 組合一個(gè)并傳遞選項(xiàng)到你的自定義配置:
const babelJest = require('babel-jest');
module.exports = babelJest.createTransformer({
presets: ['my-custom-preset'],
});
更多建議: