使用 Jest 和 Enzyme 測試 React 組件



引言

有人說在一般狀況下測試 React 組件是沒有太大用處的,可是我覺着在一些場景下是頗有必要的:javascript

  • 組件庫,
  • 開源項目,
  • 與第三方組件集成,
  • bugs, 防止復現.

我嘗試過不少的工具組合,可是最終若是會推薦給別的開發者,我更樂意去擇推薦以下組合:css

  • Jest, 一個測試框架;
  • Enzyme, React的 測試類庫;
  • enzyme-to-json 轉換 Enzyme 包裝類型匹配 Jest 的快照

我常常在測試中使用的是淺渲染和 Jest 快照測試。html

在 Jest 進行快照測試java

Shallow rendering

淺渲染指的是將一個組件渲染成虛擬 DOM 對象,可是隻渲染第一層,不渲染全部子組件。因此即便你對子組件作了一下改動卻不會影響淺渲染的輸出結果。或者是引入的子組件中發生了 bug,也不會對父組件的淺渲染結果產生影響。淺渲染是不依賴 DOM 環境的。node

舉個例子:react

const ButtonWithIcon = ({icon, children}) => (
    <button><Icon icon={icon} />{children}</button> ); 複製代碼

在 React 中將會被渲染成以下:git

<button>
    <i class="icon icon_coffee"></i>
    Hello Jest!
</button>
複製代碼

可是在淺渲染中只會被渲染成以下結果:github

<button>
    <Icon icon="coffee" /> Hello Jest! </button>
複製代碼

須要注意的是 Icon 組件並未被渲染出來。npm

快照測試

Jest 快照就像那些帶有由文本字符組合而成表達窗口和按鈕的靜態UI:它是存儲在文本文件中的組件的渲染輸出。json

你能夠告訴 Jest 哪些組件輸出的 UI 不會有意外的改變,那麼 Jest 在運行時會將其保存到以下所示的文件中:

exports[`test should render a label 1`] = ` <label className="isBlock"> Hello Jest! </label> `;

exports[`test should render a small label 1`] = ` <label className="isBlock isSmall"> Hello Jest! </label> `;
複製代碼

每次更改組件時,Jest 都會與當前測試的值進行比較並顯示差別,而且會在你作出修改是要求你更新快照。

除了測試以外,Jest 將快照存儲在相似 __snapshots __ / Label.spec.js.snap 這樣的文件中,同時你須要提交這些文件。

爲何選擇 Jest

  • 運行速度很是快。
  • 能夠進行快照測試。
  • 交互式的監控模式,只會測試有過修改的部分。
  • 錯誤信息很詳細。
  • 配置簡單。
  • Mocks 和 spies 支持.
  • 經過命令行能夠生成測試報告.
  • 發展前景很好。
  • 不會寫出像 Chai 框架 'expect(foo).to.be.a(‘function’)' 同樣很容易出錯的斷言 'expect(foo).to.be.a.function' 由於 Jest 只會寫 'expect(foo).to.be.true' 這樣肯定正確的斷言。

爲何選擇 Enzyme

  • 便利的工具函數庫封裝,能夠處理淺渲染,靜態渲染標記以及DOM渲染。
  • jQuery 風格的API,便於使用和直觀。

配置

第一步安裝全部的依賴包括同版本依賴:

npm install --save-dev jest react-test-renderer enzyme enzyme-adapter-react-16 enzyme-to-json
複製代碼

還須要安裝 Babel 插件 babel-jest 或者 TypeScript 插件 ts-jest

更新工程的 package.json 文件:

"scripts": {
  "test": "jest",
  "test:watch": "jest --watch",
  "test:coverage": "jest --coverage"
},
"jest": {
  "setupFiles": ["./test/jestsetup.js"],
  "snapshotSerializers": ["enzyme-to-json/serializer"]
}
複製代碼

配置項 'snapshotSerializers' 容許你經過配置 'enzyme-to-json',把 Enzyme 的封裝類型傳給 'Jest' 的快照匹配項中,從而不須要手動進行轉化。

建立一個 test/jestsetup.js 的文件來自定義 Jest 的運行環境(上面的 setupFiles 配置項)

import Enzyme, { shallow, render, mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
// React 16 Enzyme adapter
Enzyme.configure({ adapter: new Adapter() });
// Make Enzyme functions available in all test files without importing
global.shallow = shallow;
global.render = render;
global.mount = mount;
複製代碼

針對 css 模塊也能夠添加下面的配置到package.json

"jest": {
  "moduleNameMapper": {
    "^.+\\.(css|scss)$": "identity-obj-proxy"
  }
}
複製代碼

And run:

同時安裝依賴:

npm install --save-dev identity-obj-proxy
複製代碼

注意 identity-obj-proxy 依賴的 node 版本是 Node 4或者 Node 5須要開啓 'harmony-proxies'

測試組件的渲染

對於大部分沒有交互的組件,下面的測試用例已經足夠:

test('render a label', () => {
    const wrapper = shallow(
        <Label>Hello Jest!</Label>
    );
    expect(wrapper).toMatchSnapshot();
});

test('render a small label', () => {
    const wrapper = shallow(
        <Label small>Hello Jest!</Label>
    );
    expect(wrapper).toMatchSnapshot();
});

test('render a grayish label', () => {
    const wrapper = shallow(
        <Label light>Hello Jest!</Label>
    );
    expect(wrapper).toMatchSnapshot();
});
複製代碼

Props 測試

有的時候若是你想測試的更精確和看到真實的值。那樣的話須要在 Enzyme API 中使用 Jest的 斷言。

test('render a document title', () => {
    const wrapper = shallow(
        <DocumentTitle title="Events" />
    );
    expect(wrapper.prop('title')).toEqual('Events');
});

test('render a document title and a parent title', () => {
    const wrapper = shallow(
        <DocumentTitle title="Events" parent="Event Radar" />
    );
    expect(wrapper.prop('title')).toEqual('Events — Event Radar');
});
複製代碼

有的時候你不能用快照。好比組件裏面有隨機ID像下面的代碼:

test('render a popover with a random ID', () => {
    const wrapper = shallow(
        <Popover>Hello Jest!</Popover>
    );
    expect(wrapper.prop('id')).toMatch(/Popover\d+/);
});
複製代碼

事件測試

你能夠模擬相似 'click' 或者 'change'這樣的事件而後把組件和快照作比較:

test('render Markdown in preview mode', () => {
    const wrapper = shallow(
        <MarkdownEditor value="*Hello* Jest!" /> ); expect(wrapper).toMatchSnapshot(); wrapper.find('[name="toggle-preview"]').simulate('click'); expect(wrapper).toMatchSnapshot(); }); 複製代碼

有的時候你想要測試一個子組件中一個元素是怎樣影響組件的。你須要使用 Enzyme的 mount 方法來渲染一個真實的 DOM。

test('open a code editor', () => {
    const wrapper = mount(
        <Playground code={code} /> ); expect(wrapper.find('.ReactCodeMirror')).toHaveLength(0); wrapper.find('button').simulate('click'); expect(wrapper.find('.ReactCodeMirror')).toHaveLength(1); }); 複製代碼

測試事件處理

相似於在事件測試中,由使用快照測試組件的輸出呈現替換爲使用Jest的mock函數來測試事件處理程序自己:

test('pass a selected value to the onChange handler', () => {
    const value = '2';
    const onChange = jest.fn();
    const wrapper = shallow(
        <Select items={ITEMS} onChange={onChange} /> ); expect(wrapper).toMatchSnapshot(); wrapper.find('select').simulate('change', { target: { value }, }); expect(onChange).toBeCalledWith(value); }); 複製代碼

不單單是JSX

Jest使用JSON進行快照測試,所以你能夠測試返回JSON的任何函數,方法與測試組件相同:

test('accept custom properties', () => {
    const wrapper = shallow(
        <Layout flexBasis={0} flexGrow={1} flexShrink={1} flexWrap="wrap" justifyContent="flex-end" alignContent="center" alignItems="center" /> ); expect(wrapper.prop('style')).toMatchSnapshot(); }); 複製代碼

調試與故障排除

調試淺層渲染器輸出

Use Enzyme’s debug method to print shallow renderer’s output: 使用Enzyme的調試方法打印千層渲染器的輸出:

const wrapper = shallow(/*~*/);
console.log(wrapper.debug());
複製代碼

啓用覆蓋範圍的失敗測試

當你的測試失敗時,帶有覆蓋範圍標誌的diff以下所示:

-<Button
+<Component
複製代碼

嘗試將箭頭函數組件替換爲常規函數組建:

- export default const Button = ({ children }) => {
+ export default function Button({ children }) {
複製代碼

requestAnimationFrame 錯誤

當你運行你的測試時,你可能會看到以下錯誤:

console.error node_modules/fbjs/lib/warning.js:42
  Warning: React depends on requestAnimationFrame. Make sure that you load a polyfill in older browsers. http://fb.me/react-polyfills
複製代碼

React 16依賴於requestAnimationFrame,所以你須要在你的測試代碼中添加一個polyfill

// test/jestsetup.js
import 'raf/polyfill';
複製代碼

參考來源

相關文章
相關標籤/搜索