测试React程序

    如果你是 React 新手,我们建议使用 Create React App。 它已经包含了 ! 您只需要添加 来渲染快照。

    运行

    • npm
    • Yarn
    1. yarn add --dev react-test-renderer

    如果你已经有一个应用,你仅需要安装一些包来使他们运行起来。 我们使用babel-jest包和babel-preset-react,从而在测试环境中转换我们代码。 可参考

    运行

    • npm
    • Yarn
    1. npm install --save-dev jest babel-jest @babel/preset-env @babel/preset-react react-test-renderer
    1. yarn add --dev jest babel-jest @babel/preset-env @babel/preset-react react-test-renderer

    你的package.json文件应该像下面这样(<current-version>是当前包的最新版本号) 请添加脚本项目和 jest 配置: 请添加脚本项目和 jest 配置:

    1. {
    2. "dependencies": {
    3. "react": "<current-version>",
    4. "react-dom": "<current-version>"
    5. },
    6. "devDependencies": {
    7. "@babel/preset-env": "<current-version>",
    8. "@babel/preset-react": "<current-version>",
    9. "babel-jest": "<current-version>",
    10. "jest": "<current-version>",
    11. "react-test-renderer": "<current-version>"
    12. },
    13. "scripts": {
    14. "test": "jest"
    15. }
    16. }

    babel.config.js

    1. module.exports = {
    2. presets: [
    3. '@babel/preset-env',
    4. ['@babel/preset-react', {runtime: 'automatic'}],
    5. ],
    6. };

    准备工作已经完成!

    让我们来为一个渲染超链接的 Link 组件创建

    Link.js

    1. import {useState} from 'react';
    2. const STATUS = {
    3. HOVERED: 'hovered',
    4. NORMAL: 'normal',
    5. };
    6. export default function Link({page, children}) {
    7. const [status, setStatus] = useState(STATUS.NORMAL);
    8. const onMouseEnter = () => {
    9. setStatus(STATUS.HOVERED);
    10. };
    11. const onMouseLeave = () => {
    12. setStatus(STATUS.NORMAL);
    13. };
    14. return (
    15. <a
    16. className={status}
    17. href={page || '#'}
    18. onMouseEnter={onMouseEnter}
    19. >
    20. {children}
    21. </a>
    22. );
    23. }

    现在,使用React的test renderer和Jest的快照特性来和组件交互,获得渲染结果和生成快照文件:

    Link.test.js

    1. import renderer from 'react-test-renderer';
    2. import Link from '../Link';
    3. it('changes the class when hovered', () => {
    4. const component = renderer.create(
    5. <Link page="http://www.facebook.com">Facebook</Link>,
    6. );
    7. let tree = component.toJSON();
    8. expect(tree).toMatchSnapshot();
    9. // manually trigger the callback
    10. renderer.act(() => {
    11. tree.props.onMouseEnter();
    12. });
    13. // re-rendering
    14. tree = component.toJSON();
    15. expect(tree).toMatchSnapshot();
    16. // manually trigger the callback
    17. renderer.act(() => {
    18. tree.props.onMouseLeave();
    19. });
    20. // re-rendering
    21. tree = component.toJSON();
    22. expect(tree).toMatchSnapshot();
    23. });

    __tests__/__snapshots__/Link.test.js.snap

    下次你运行测试时,渲染的结果将会和之前创建的快照进行比较。 代码变动时,快照也应该被提交。 当快照测试失败,你需要去检查是否是你想要或不想要的变动。 如果变动符合预期,你可以通过jest -u调用Jest从而重写存在的快照。

    The code for this example is available at examples/snapshot.

    Snapshot Testing with Mocks, Enzyme and React 16+

    快照测试在 Enzyme 和 React 16+ 中使用时有一个注意事项。 如果您使用以下方式模拟模块:

    1. jest.mock('../SomeDirectory/SomeComponent', () => 'SomeComponent');

    然后您将在控制台中看到警告:

    1. Warning: <SomeComponent /> is using uppercase HTML. Always use lowercase HTML tags in React.
    2. # Or:
    3. 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.
    4. # Or:
    5. 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 触发这些警告,取决于它是如何检查元素类型的,在这些检查中模拟模块会失败。 您可以:

    1. 渲染为纯文本。 这种方式你看不到传递给模拟组件的Props,但最直观。

    jest.mock(‘./SomeComponent’, () => () => ‘SomeComponent’);

    1. ```
    1. 渲染为自定义元素。 DOM的“自定义元素“不会检查任何属性,所以也不会触发warnings。 他们都是小写的、中划线分割的单词。

      1. jest.mock('./Widget', () => () => <mock-widget />);
    2. 使用 react-test-renderer。 这个test渲染器不校验元素类型,并乐意接受SomeComponent作为参数。 你可以使用react-test-renderer进行快照检测,使用单独使用Enzyme进行组件行为监测。

    3. 禁用所有警告(在jest setup file中):

      1. jest.mock('fbjs/lib/warning', () => require('fbjs/lib/emptyFunction'));

      这是下策,因为所有有用的警告也都会丢失。 不过,依然有其适用场景。例如我们需要测试 react-native的组件编译为DOM,很多警告是无关紧要的。 另一个方式是使用控制台忽略特定的警告信息。

    react-testing-library

    • npm
    • Yarn
    1. npm install --save-dev @testing-library/react

    这里我们实现一个在两个标签之间切换的复选框。

    CheckboxWithLabel.js

    1. import {useState} from 'react';
    2. export default function CheckboxWithLabel({labelOn, labelOff}) {
    3. const [isChecked, setIsChecked] = useState(false);
    4. const onChange = () => {
    5. setIsChecked(!isChecked);
    6. };
    7. return (
    8. <input type="checkbox" checked={isChecked} onChange={onChange} />
    9. {isChecked ? labelOn : labelOff}
    10. </label>
    11. );
    12. }

    __tests__/CheckboxWithLabel-test.js

    1. import {cleanup, fireEvent, render} from '@testing-library/react';
    2. // Note: running cleanup afterEach is done automatically for you in @testing-library/react@9.0.0 or higher
    3. // unmount and cleanup DOM after the test is finished.
    4. afterEach(cleanup);
    5. it('CheckboxWithLabel changes the text after click', () => {
    6. const {queryByLabelText, getByLabelText} = render(
    7. <CheckboxWithLabel labelOn="On" labelOff="Off" />,
    8. );
    9. expect(queryByLabelText(/off/i)).toBeTruthy();
    10. fireEvent.click(getByLabelText(/off/i));
    11. expect(queryByLabelText(/on/i)).toBeTruthy();
    12. });

    The code for this example is available at examples/react-testing-library.

    Enzyme

    • npm
    • Yarn
    1. npm install --save-dev enzyme
    1. yarn add --dev enzyme

    如果你的React版本低于15.5.0,则也需要再安装react-addons-test-utils

    我们使用react-testing-library的方式重写一遍上面的用例。 We use Enzyme’s shallow renderer in this example.

    __tests__/CheckboxWithLabel-test.js

    1. import Enzyme, {shallow} from 'enzyme';
    2. import Adapter from 'enzyme-adapter-react-16';
    3. import CheckboxWithLabel from '../CheckboxWithLabel';
    4. Enzyme.configure({adapter: new Adapter()});
    5. it('CheckboxWithLabel changes the text after click', () => {
    6. // Render a checkbox with label in the document
    7. const checkbox = shallow(<CheckboxWithLabel labelOn="On" labelOff="Off" />);
    8. expect(checkbox.text()).toEqual('Off');
    9. checkbox.find('input').simulate('change');
    10. expect(checkbox.text()).toEqual('On');
    11. });

    The code for this example is available at .

    如果你需要更多高级功能,你也可以自定义你的转译器。 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:

    custom-transformer.js

    1. 'use strict';
    2. const {transform} = require('@babel/core');
    3. const jestPreset = require('babel-preset-jest');
    4. module.exports = {
    5. process(src, filename) {
    6. const result = transform(src, {
    7. filename,
    8. presets: [jestPreset],
    9. });
    10. return result || src;
    11. },
    12. };

    别忘记安装 @babel/core and babel-preset-jest

    为了使这个与 Jest 一起工作,您需要更新您的 Jest 配置:"transform": {"\\.js$": "path/to/custom-transformer.js"}

    1. const babelJest = require('babel-jest');
    2. module.exports = babelJest.createTransformer({

    详情见 。