测试React程序
在Facebook,我们使用 Jest 测试 React 应用程序。
安装
使用Create React App
如果你是 React 新手,我们建议使用 Create React App。 它已经包含了 可用的 Jest! 您只需要添加 react-test-renderer
来渲染快照。
运行
- npm
- Yarn
- pnpm
npm install --save-dev react-test-renderer
yarn add --dev react-test-renderer
pnpm add --save-dev react-test-renderer
不使用Create React App
如果你已经有一个应用,你仅需要安装一些包来使他们运行起来。 我们使用babel-jest
包和babel-preset-react
,从而在测试环境中转换我们代码。 可参考使用babel
运行
- npm
- Yarn
- pnpm
npm install --save-dev jest babel-jest @babel/preset-env @babel/preset-react react-test-renderer
yarn add --dev jest babel-jest @babel/preset-env @babel/preset-react react-test-renderer
pnpm add --save-dev jest babel-jest @babel/preset-env @babel/preset-react react-test-renderer
你的package.json
文件应该像下面这样(<current-version>
是当前包的最新版本号) 请添加脚本项目和 jest 配置: 请添加脚本项目和 jest 配置:
{
"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"
}
}
module.exports = {
presets: [
'@babel/preset-env',
['@babel/preset-react', {runtime: 'automatic'}],
],
};
准备工作已经完成!
快照测试
让我们来为一个渲染超链接的 Link 组件创建快照测试
import {useState} from 'react';
const STATUS = {
HOVERED: 'hovered',
NORMAL: 'normal',
};
export default function Link({page, children}) {
const [status, setStatus] = useState(STATUS.NORMAL);
const onMouseEnter = () => {
setStatus(STATUS.HOVERED);
};
const onMouseLeave = () => {
setStatus(STATUS.NORMAL);
};
return (
<a
className={status}
href={page || '#'}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
{children}
</a>
);
}
Examples are using Function components, but Class components can be tested in the same way. 相关区别请查看:React: Function and Class Components. 注意 ,我们希望对于Class组件,Jest应该更多的去测试Props而不是Class中的函数。
现在,使用React的test renderer和Jest的快照特性来和组件交互,获得渲染结果和生成快照文件:
import renderer from 'react-test-renderer';
import Link from '../Link';
it('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
renderer.act(() => {
tree.props.onMouseEnter();
});
// re-rendering
tree = component.toJSON();
expect(tree).toMatchSnapshot();
// manually trigger the callback
renderer.act(() => {
tree.props.onMouseLeave();
});
// re-rendering
tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
当你运行 npm test
或者 jest
,将产生一个像下面的文件:
exports[`changes the class when hovered 1`] = `
<a
className="normal"
href="http://www.facebook.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Facebook
</a>
`;
exports[`changes the class when hovered 2`] = `
<a
className="hovered"
href="http://www.facebook.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Facebook
</a>
`;
exports[`changes the class when hovered 3`] = `
<a
className="normal"
href="http://www.facebook.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Facebook
</a>
`;
下次你运行测试时,渲染的结果将会和之前创建的快照进行比较。 代码变动时,快照也应该被提交。 当快照测试失败,你需要去检查是否是你想要或不想要的变动。 如果变动符合预期,你可以通过jest -u
调用Jest从而重写存在的快照。
The code for this example is available at examples/snapshot.
Snapshot Testing with Mocks, Enzyme and React 16+
快照测试在 Enzyme 和 React 16+ 中使用时有一个注意事项。 如果您使用以下方式模拟模块:
jest.mock('../SomeDirectory/SomeComponent', () => 'SomeComponent');
然后您将在控制台中看到警告:
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. 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 触发这些警告,取决于它是如何检查元素类型的,在这些检查中模拟模块会失败。 您可以:
- 渲染为纯文本。 这种方式你看不到传递给模拟组件的Props,但最直观。
jest.mock('./SomeComponent', () => () => 'SomeComponent');
```
- 渲染为自定义元素。 DOM的“自定义元素“不会检查任何属性,所以也不会触发warnings。 他们都是小写的、中划线分割的单词。
jest.mock('./Widget', () => () => <mock-widget />);
- 使用
react-test-renderer
。 这个test渲染器不校验元素类型,并乐意接受SomeComponent
作为参数。 你可以使用react-test-renderer
进行快照检测,使用单独使用Enzyme进行组件行为监测。 - 禁用所有警告(在jest setup file中):这是下策,因为所有有用的警告也都会丢失。 不过,依然有其适用场景。例如我们需要测试 react-native的组件编译为DOM,很多警告是无关紧要的。 另一个方式是使用控制台忽略特定的警告信息。
jest.mock('fbjs/lib/warning', () => require('fbjs/lib/emptyFunction'));
DOM测试
If you'd like to assert, and manipulate your rendered components you can use react-testing-library, Enzyme, or React's TestUtils. 接下来我们讲两个 react-testing-library 和Enzyme的例子。
react-testing-library
- npm
- Yarn
- pnpm
npm install --save-dev @testing-library/react
yarn add --dev @testing-library/react
pnpm add --save-dev @testing-library/react
这里我们实现一个在两个标签之间切换的复选框。
import {useState} from 'react';
export default function CheckboxWithLabel({labelOn, labelOff}) {
const [isChecked, setIsChecked] = useState(false);
const onChange = () => {
setIsChecked(!isChecked);
};
return (
<label>
<input type="checkbox" checked={isChecked} onChange={onChange} />
{isChecked ? labelOn : labelOff}
</label>
);
}
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();
});
The code for this example is available at examples/react-testing-library.
Enzyme
- npm
- Yarn
- pnpm
npm install --save-dev enzyme
yarn add --dev enzyme
pnpm add --save-dev enzyme
如果你的React版本低于15.5.0,则也需要再安装react-addons-test-utils
我们使用react-testing-library的方式重写一遍上面的用例。 We use Enzyme's shallow renderer in this example.
import Enzyme, {shallow} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import CheckboxWithLabel from '../CheckboxWithLabel';
Enzyme.configure({adapter: new Adapter()});
it('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()).toBe('Off');
checkbox.find('input').simulate('change');
expect(checkbox.text()).toBe('On');
});
自定义转译器
如果你需要更多高级功能,你也可以自定义你的转译器。 If you need more advanced functionality, you can also build your own transformer. Instead of using babel-jest
, here is an example of using @babel/core
:
'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 || src;
},
};
别忘记安装 @babel/core
and 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'],
});
详情见 dedicated docs。