使用 enzyme 測試 React Native 中的組件
從 v0.18 開始,React Native 使用 React 作為依賴項而非庫的分支版本,這表示現在可以用 enzyme 的 shallow
來搭配 React Native 中的組件。
遺憾的是,React Native 有許多環境依賴項,在沒有主機裝置的情況下很難模擬。
當你想要在一般持續整合伺服器(例如 Travis)中執行測試套件時,這可能會很難處理。
要使用 enzyme 測試 React Native,你目前需要組態一個轉接器,並載入一個模擬的 DOM。
設定接合器
在討論 React Native 接合器時,可以使用標準接合器,例如 'enzyme-adapter-react-16'
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({ adapter: new Adapter() });
載入模擬的 DOM 與 JSDOM
在 React Native 接合器出現之前,如需使用 enzyme 的 mount
,必須載入模擬的 DOM。
有些人成功使用過 react-native-mock-renderer,不過建議的方式是使用 https://github.com/tmpvar/jsdom,JSDOM 文件頁面上也有 enzyme 的相關文件。
JSDOM 會允許所有你預期出現的 enzyme
行為。此方法也可以使用 Jest 擷取快照測試,不過並不建議這麼做,而且只能透過 wrapper.debug()
來支援。
在缺乏 className props 時使用 enzyme 的 find
值得注意的是,React Native 允許將 testID 道具作為選擇器,類似標準 React 中的 className
。
<View key={key} style={styles.todo} testID="todo-item">
<Text testID="todo-title" style={styles.title}>{todo.title}</Text>
</View>
expect(wrapper.findWhere((node) => node.prop('testID') === 'todo-item')).toExist();
Jest 與 JSDOM 替代項的預設範例組態
要在你測試架構中進行必要的組態,建議使用設定腳本,例如使用 Jest 的 setupFilesAfterEnv
設定。
在你的專案根目錄建立或更新一個 jest.config.js
檔案,包含 setupFilesAfterEnv
設定。
// jest.config.js
module.exports = {
// Load setup-tests.js before test execution
setupFilesAfterEnv: '<rootDir>setup-tests.js',
// ...
};
然後建立或更新 setupFilesAfterEnv
中指定的檔案,在本範例中是在專案根目錄中的 setup-tests.js
// setup-tests.js
import 'react-native';
import 'jest-enzyme';
import Adapter from 'enzyme-adapter-react-16';
import Enzyme from 'enzyme';
/**
* Set up DOM in node.js environment for Enzyme to mount to
*/
const { JSDOM } = require('jsdom');
const jsdom = new JSDOM('<!doctype html><html><body></body></html>');
const { window } = jsdom;
function copyProps(src, target) {
Object.defineProperties(target, {
...Object.getOwnPropertyDescriptors(src),
...Object.getOwnPropertyDescriptors(target),
});
}
global.window = window;
global.document = window.document;
global.navigator = {
userAgent: 'node.js',
};
copyProps(window, global);
/**
* Set up Enzyme to mount to DOM, simulate events,
* and inspect the DOM in tests.
*/
Enzyme.configure({ adapter: new Adapter() });
設定 enzyme 與其他測試函式庫並動態加入 JSDOM
更新 setupFilesAfterEnv
中指定的檔案,在本範例中是在專案根目錄中的 setup-tests.js
import 'react-native';
import 'jest-enzyme';
import Adapter from 'enzyme-adapter-react-16';
import Enzyme from 'enzyme';
/**
* Set up Enzyme to mount to DOM, simulate events,
* and inspect the DOM in tests.
*/
Enzyme.configure({ adapter: new Adapter() });
建立一個單獨的測試檔案
建立一個以 enzyme.test.ts 為開頭的檔案,例如 component.enzyme.test.js
/**
* @jest-environment jsdom
*/
import React from 'react';
import { mount } from 'enzyme';
import { Text } from '../../../component/text';
describe('Component tested with airbnb enzyme', () => {
test('App mount with enzyme', () => {
const wrapper = mount(<Text />);
// other tests operations
});
});
最重要的部分是要確保這個測試是以將 jestEnvironment
設定為 jsdom
的方式執行 - 一種方式是在檔案開頭加上一個 /* @jest-environment jsdom */
註解。
然後你就能開始寫測試了!
請注意,你可能想要執行一些關於原生組件的額外模擬,或者如果你想對 React Native 組件進行快照測試。注意在這種情況下,你可能需要模擬 React Navigation 的 KeyGenerator
,以避免隨機的 React 鍵會導致快照總是失敗。
import React from 'react';
import renderer from 'react-test-renderer';
import { mount, ReactWrapper } from 'enzyme';
import { Provider } from 'mobx-react';
import { Text } from 'native-base';
import { TodoItem } from './todo-item';
import { TodoList } from './todo-list';
import { todoStore } from '../../stores/todo-store';
// https://github.com/react-navigation/react-navigation/issues/2269
// React Navigation generates random React keys, which makes
// snapshot testing fail. Mock the randomness to keep from failing.
jest.mock('react-navigation/src/routers/KeyGenerator', () => ({
generateKey: jest.fn(() => 123),
}));
describe('todo-list', () => {
describe('enzyme tests', () => {
it('can add a Todo with Enzyme', () => {
const wrapper = mount(
<Provider keyLength={0} todoStore={todoStore}>
<TodoList />
</Provider>,
);
const newTodoText = 'I need to do something...';
const newTodoTextInput = wrapper.find('Input').first();
const addTodoButton = wrapper
.find('Button')
.findWhere((w) => w.text() === 'Add Todo')
.first();
newTodoTextInput.props().onChangeText(newTodoText);
// Enzyme usually allows wrapper.simulate() alternatively, but this doesn't support 'press' events.
addTodoButton.props().onPress();
// Make sure to call update if external events (e.g. Mobx state changes)
// result in updating the component props.
wrapper.update();
// You can either check for a testID prop, similar to className in React:
expect(
wrapper.findWhere((node) => node.prop('testID') === 'todo-item'),
).toExist();
// Or even just find a component itself, if you broke the JSX out into its own component:
expect(wrapper.find(TodoItem)).toExist();
// You can even do snapshot testing,
// if you pull in enzyme-to-json and configure
// it in snapshotSerializers in package.json
expect(wrapper.find(TodoList)).toMatchSnapshot();
});
});
});