Jest 測(cè)試 React 應(yīng)用程序

2021-09-18 20:39 更新

在Facebook,我們使用 Jest 測(cè)試 React 應(yīng)用程序。

安裝

使用Create React App

如果你是 React 新手,我們建議使用 ?Create React App?。 它已經(jīng)包含了可用的 Jest! 你只需要添加 ?react-test-renderer? 來渲染快照。

運(yùn)行

  1. yarn add --dev react-test-renderer

不使用Create React App

如果你已經(jīng)有一個(gè)應(yīng)用,你僅需要安裝一些包來使他們運(yùn)行起來。 我們使用?babel-jest?包和?babel-preset-react?,從而在測(cè)試環(huán)境中轉(zhuǎn)換我們代碼。 可參考使用babel

運(yùn)行

  1. 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 配置:

  1. // package.json
  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. }
  1. // babel.config.js
  2. module.exports = {
  3. presets: ['@babel/preset-env', '@babel/preset-react'],
  4. };

準(zhǔn)備工作已經(jīng)完成!

快照測(cè)試

讓我們來為一個(gè)渲染超鏈接的 Link 組件創(chuàng)建快照測(cè)試

  1. // Link.react.js
  2. import React from 'react';
  3. const STATUS = {
  4. HOVERED: 'hovered',
  5. NORMAL: 'normal',
  6. };
  7. export default class Link extends React.Component {
  8. constructor(props) {
  9. super(props);
  10. this._onMouseEnter = this._onMouseEnter.bind(this);
  11. this._onMouseLeave = this._onMouseLeave.bind(this);
  12. this.state = {
  13. class: STATUS.NORMAL,
  14. };
  15. }
  16. _onMouseEnter() {
  17. this.setState({class: STATUS.HOVERED});
  18. }
  19. _onMouseLeave() {
  20. this.setState({class: STATUS.NORMAL});
  21. }
  22. render() {
  23. return (
  24. <a
  25. className={this.state.class}
  26. href={this.props.page || '#'}
  27. onMouseEnter={this._onMouseEnter}
  28. onMouseLeave={this._onMouseLeave}
  29. >
  30. {this.props.children}
  31. </a>
  32. );
  33. }
  34. }

現(xiàn)在,使用React的test renderer和Jest的快照特性來和組件交互,獲得渲染結(jié)果和生成快照文件:

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

當(dāng)你運(yùn)行 ?npm test? 或者 ?jest?,將產(chǎn)生一個(gè)像下面的文件:

  1. // __tests__/__snapshots__/Link.react.test.js.snap
  2. exports[`Link changes the class when hovered 1`] = `
  3. <a
  4. className="normal"
  5. rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank"
  6. onMouseEnter={[Function]}
  7. onMouseLeave={[Function]}>
  8. Facebook
  9. </a>
  10. `;
  11. exports[`Link changes the class when hovered 2`] = `
  12. <a
  13. className="hovered"
  14. rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank"
  15. onMouseEnter={[Function]}
  16. onMouseLeave={[Function]}>
  17. Facebook
  18. </a>
  19. `;
  20. exports[`Link changes the class when hovered 3`] = `
  21. <a
  22. className="normal"
  23. rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank"
  24. onMouseEnter={[Function]}
  25. onMouseLeave={[Function]}>
  26. Facebook
  27. </a>
  28. `;

下次你運(yùn)行測(cè)試時(shí),渲染的結(jié)果將會(huì)和之前創(chuàng)建的快照進(jìn)行比較??煺諔?yīng)與代碼更改一起提交。當(dāng)快照測(cè)試失敗,你需要去檢查是否是你想要或不想要的變動(dòng)。 如果變動(dòng)符合預(yù)期,你可以通過?jest -u?調(diào)用Jest從而重寫存在的快照。

該示例代碼在 examples/snapshot

快照測(cè)試與 Mocks, Enzyme 和 React 16

在使用 Enzyme 和 React 16+ 時(shí),有一個(gè)關(guān)于快照測(cè)試的警告。如果你使用以下樣式模擬模塊:

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

然后你會(huì)在控制臺(tái)看到警告:

  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.

React 16 由于檢查元素類型的方式而觸發(fā)這些警告,并且模擬模塊未通過這些檢查。你的選擇是:

  1. 渲染為文本。這樣你就不會(huì)在快照中看到傳遞給模擬組件的道具,但它很簡(jiǎn)單:
    jest.mock('./SomeComponent',()=>()=>'SomeComponent');
  2. 呈現(xiàn)為自定義元素。DOM“自定義元素”不會(huì)被檢查任何東西,也不應(yīng)該發(fā)出警告。它們是小寫的,名稱中有一個(gè)破折號(hào)。
    jest.mock('./Widget',()=>()=><mock-widget />);
  3. 使用react-test-renderer. 測(cè)試渲染器不關(guān)心元素類型,并且很樂意接受例如SomeComponent. 你可以使用測(cè)試渲染器檢查快照,并使用酶單獨(dú)檢查組件行為。
  4. 一起禁用警告(應(yīng)該在你的 jest 設(shè)置文件中完成):
    jest.mock('fbjs/lib/warning',()=>require('fbjs/lib/emptyFunction'));

        這通常不應(yīng)該是你的選擇,因?yàn)橛杏玫木婵赡軙?huì)丟失。然而,在某些情況下,例如在測(cè)試 react-native 的組件時(shí),我們將 react-native 標(biāo)簽渲染到 DOM 中,許多警告是無關(guān)緊要的。另一種選擇是調(diào)整 console.warn 并抑制特定警告。

DOM測(cè)試

如果你想斷言和操作你的渲染組件,你可以使用react-testing-library、Enzyme或 React 的TestUtils。以下兩個(gè)示例使用 react-testing-library 和 Enzyme。

反應(yīng)測(cè)試庫(kù)

你必須運(yùn)行?yarn add --dev @testing-library/react?才能使用 react-testing-library。

讓我們實(shí)現(xiàn)一個(gè)在兩個(gè)標(biāo)簽之間交換的復(fù)選框:

  1. // CheckboxWithLabel.js
  2. import React from 'react';
  3. export default class CheckboxWithLabel extends React.Component {
  4. constructor(props) {
  5. super(props);
  6. this.state = {isChecked: false};
  7. // bind manually because React class components don't auto-bind
  8. // https://reactjs.org/blog/2015/01/27/react-v0.13.0-beta-1.html#autobinding
  9. this.onChange = this.onChange.bind(this);
  10. }
  11. onChange() {
  12. this.setState({isChecked: !this.state.isChecked});
  13. }
  14. render() {
  15. return (
  16. <label>
  17. <input
  18. type="checkbox"
  19. checked={this.state.isChecked}
  20. onChange={this.onChange}
  21. />
  22. {this.state.isChecked ? this.props.labelOn : this.props.labelOff}
  23. </label>
  24. );
  25. }
  26. }
  1. // __tests__/CheckboxWithLabel-test.js
  2. import React from 'react';
  3. import {cleanup, fireEvent, render} from '@testing-library/react';
  4. import CheckboxWithLabel from '../CheckboxWithLabel';
  5. // Note: running cleanup afterEach is done automatically for you in @testing-library/react@9.0.0 or higher
  6. // unmount and cleanup DOM after the test is finished.
  7. afterEach(cleanup);
  8. it('CheckboxWithLabel changes the text after click', () => {
  9. const {queryByLabelText, getByLabelText} = render(
  10. <CheckboxWithLabel labelOn="On" labelOff="Off" />,
  11. );
  12. expect(queryByLabelText(/off/i)).toBeTruthy();
  13. fireEvent.click(getByLabelText(/off/i));
  14. expect(queryByLabelText(/on/i)).toBeTruthy();
  15. });

這個(gè)例子的代碼可以在examples/react-testing-library 找到。

Enzyme

你必須運(yùn)行?yarn add --dev enzyme?才能使用 Enzyme。如果你使用的是低于 15.5.0 的 React 版本,你還需要安裝?react-addons-test-utils?.

讓我們用Enzyme而不是反應(yīng)測(cè)試庫(kù)從上面重寫測(cè)試。在這個(gè)例子中我們使用了 Enzyme 的淺渲染器。

  1. // __tests__/CheckboxWithLabel-test.js
  2. import React from 'react';
  3. import {shallow} from 'enzyme';
  4. import CheckboxWithLabel from '../CheckboxWithLabel';
  5. test('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. });

此示例的代碼可從examples/enzyme 獲得

自定義轉(zhuǎn)譯器

如果你需要更高級(jí)的功能,還可以構(gòu)建自己的變壓器。這里沒有使用 babel-jest,而是使用 babel 的一個(gè)例子:

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

不要忘記安裝@?babel/core?和?babel-preset-jest?包以使本示例正常工作。

為了使這個(gè)與 Jest 一起工作,你需要更新你的 Jest 配置:?"transform": {"\\.js$": "path/to/custom-transformer.js"}?。

如果你想建立一個(gè)帶 babel 支持的轉(zhuǎn)譯器,你還可以使用 babel-jest 組合一個(gè)并傳遞選項(xiàng)到你的自定義配置:

  1. const babelJest = require('babel-jest');
  2. module.exports = babelJest.createTransformer({
  3. presets: ['my-custom-preset'],
  4. });


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)