測試 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
套件和 react
babel 預設值來轉換測試環境中的程式碼。另請參閱 使用 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 設定項目
{
"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>
);
}
範例使用函式元件,但類別元件也可以用相同方式進行測試。請參閱React:函式和類別元件。提醒對於類別元件,我們預期 Jest 用於測試屬性,而不是直接測試方法。
現在,讓我們使用 React 的測試渲染器和 Jest 的快照功能與元件互動,並擷取已渲染的輸出並建立快照檔案
import renderer from 'react-test-renderer';
import Link from '../Link';
it('changes the class when hovered', () => {
const component = renderer.create(
<Link page="https://127.0.0.1">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();
});
當您執行 yarn test
或 jest
時,這將產生類似以下內容的輸出檔案
exports[`changes the class when hovered 1`] = `
<a
className="normal"
href="https://127.0.0.1"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Facebook
</a>
`;
exports[`changes the class when hovered 2`] = `
<a
className="hovered"
href="https://127.0.0.1"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Facebook
</a>
`;
exports[`changes the class when hovered 3`] = `
<a
className="normal"
href="https://127.0.0.1"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Facebook
</a>
`;
下次執行測試時,已渲染的輸出將與先前建立的快照進行比較。快照應與程式碼變更一起提交。當快照測試失敗時,您需要檢查它是否為預期的或意外的變更。如果變更符合預期,您可以使用 jest -u
呼叫 Jest 以覆寫現有的快照。
此範例的程式碼可在 examples/snapshot 取得。
使用 Mock、Enzyme 和 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.
React 16 會觸發這些警告,原因在於它檢查元素類型的方式,而模擬的模組無法通過這些檢查。您的選項有
- 以文字形式呈現。這樣您在快照中看不到傳遞給模擬元件的道具,但這很簡單
jest.mock('./SomeComponent', () => () => 'SomeComponent');
- 以自訂元素呈現。DOM「自訂元素」不會檢查任何內容,也不應觸發警告。它們是小寫,且名稱中帶有破折號。
jest.mock('./Widget', () => () => <mock-widget />);
- 使用
react-test-renderer
。測試渲染器不關心元素類型,並會樂於接受例如SomeComponent
。您可以使用測試渲染器檢查快照,並使用 Enzyme 分別檢查元件行為。 - 全部停用警告(應在 jest 設定檔中執行)
這通常不應該是您的選擇,因為可能會遺失有用的警告。然而,在某些情況下,例如在測試 react-native 的元件時,我們會將 react-native 標籤呈現到 DOM 中,許多警告都是不相關的。另一個選項是交換 console.warn 並抑制特定警告。
jest.mock('fbjs/lib/warning', () => require('fbjs/lib/emptyFunction'));
DOM 測試
如果您想宣告並操作已呈現的元件,可以使用 @testing-library/react、Enzyme 或 React 的 TestUtils。下列範例使用 @testing-library/react
。
@testing-library/react
- 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();
});
此範例的程式碼可於 examples/react-testing-library 取得。
自訂轉換器
如果您需要更進階的功能,也可以建立自己的轉換器。以下是使用 @babel/core
的範例,而非使用 babel-jest
'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
和 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'],
});
請參閱 專屬文件,以取得更多詳細資訊。