在 React Native 中使用 enzyme 测试组件
从 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() });
使用 JSDOM 加载模拟 DOM
要在 React Native 适配器出现之前使用 enzyme 的 mount
,必须加载模拟 DOM。
虽然有些人成功使用了 react-native-mock-renderer,但建议的方法是使用 https://github.com/tmpvar/jsdom,如 JSDOM 文档页面中为 enzyme 文档所示。
JSDOM 将允许所有您期望的 enzyme
行为。虽然 Jest 快照测试也可以与此方法一起使用,但不鼓励这样做,并且仅通过 wrapper.debug()
提供支持。
在缺少 className 属性时使用 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();
});
});
});