测试 React Native 应用
在 Facebook,我们使用 Jest 来测试 React Native 应用。
¥At Facebook, we use Jest to test React Native applications.
通过阅读以下系列文章,更深入地了解测试工作 React Native 应用示例:第 1 部分:Jest - 快照发挥作用 和 第 2 部分:Jest – Actions 和 Reducers 的 Redux 快照。
¥Get a deeper insight into testing a working React Native app example by reading the following series: Part 1: Jest – Snapshot come into play and Part 2: Jest – Redux Snapshots for your Actions and Reducers.
设置
¥Setup
从 react-native 版本 0.38 开始,运行 react-native init
时默认包含 Jest 设置。以下配置应自动添加到你的 package.json 文件中:
¥Starting from react-native version 0.38, a Jest setup is included by default when running react-native init
. The following configuration should be automatically added to your package.json file:
{
"scripts": {
"test": "jest"
},
"jest": {
"preset": "react-native"
}
}
运行 yarn test
以使用 Jest 运行测试。
¥Run yarn test
to run tests with Jest.
如果你正在升级你的 react-native 应用并且之前使用了 jest-react-native
预设,请从 package.json
文件中删除依赖并将预设更改为 react-native
。
¥If you are upgrading your react-native application and previously used the jest-react-native
preset, remove the dependency from your package.json
file and change the preset to react-native
instead.
快照测试
¥Snapshot Test
让我们为一个小型介绍组件创建一个 快照测试,其中包含一些视图、文本组件和一些样式:
¥Let's create a snapshot test for a small intro component with a few views and text components and some styles:
import React, {Component} from 'react';
import {StyleSheet, Text, View} from 'react-native';
class Intro extends Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>Welcome to React Native!</Text>
<Text style={styles.instructions}>
This is a React Native snapshot test.
</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
alignItems: 'center',
backgroundColor: '#F5FCFF',
flex: 1,
justifyContent: 'center',
},
instructions: {
color: '#333333',
marginBottom: 5,
textAlign: 'center',
},
welcome: {
fontSize: 20,
margin: 10,
textAlign: 'center',
},
});
export default Intro;
现在让我们使用 React 的测试渲染器和 Jest 的快照功能与组件交互并捕获渲染的输出并创建快照文件:
¥Now let's use React's test renderer and Jest's snapshot feature to interact with the component and capture the rendered output and create a snapshot file:
import React from 'react';
import renderer from 'react-test-renderer';
import Intro from '../Intro';
test('renders correctly', () => {
const tree = renderer.create(<Intro />).toJSON();
expect(tree).toMatchSnapshot();
});
当你运行 yarn test
或 jest
时,将生成如下输出文件:
¥When you run yarn test
or jest
, this will produce an output file like this:
exports[`Intro renders correctly 1`] = `
<View
style={
Object {
"alignItems": "center",
"backgroundColor": "#F5FCFF",
"flex": 1,
"justifyContent": "center",
}
}>
<Text
style={
Object {
"fontSize": 20,
"margin": 10,
"textAlign": "center",
}
}>
Welcome to React Native!
</Text>
<Text
style={
Object {
"color": "#333333",
"marginBottom": 5,
"textAlign": "center",
}
}>
This is a React Native snapshot test.
</Text>
</View>
`;
下次运行测试时,渲染的输出将与之前创建的快照进行比较。快照应与代码更改一起提交。当快照测试失败时,你需要检查它是有意的还是无意的更改。如果需要进行更改,你可以使用 jest -u
调用 Jest 来覆盖现有快照。
¥The next time you run the tests, the rendered output will be compared to the previously created snapshot. The snapshot should be committed along with code changes. When a snapshot test fails, you need to inspect whether it is an intended or unintended change. If the change is expected you can invoke Jest with jest -u
to overwrite the existing snapshot.
此示例的代码可在 examples/react-native 处获取。
¥The code for this example is available at examples/react-native.
预设配置
¥Preset configuration
该预设设置了环境,并且非常有态度,并且基于我们发现在 Facebook 有用的内容。所有配置选项都可以被覆盖,就像在不使用预设时可以自定义它们一样。
¥The preset sets up the environment and is very opinionated and based on what we found to be useful at Facebook. All of the configuration options can be overwritten just as they can be customized when no preset is used.
环境
¥Environment
react-native
附带 Jest 预设,因此 package.json
的 jest.preset
字段应指向 react-native
。预设是一个模仿 React Native 应用环境的节点环境。由于它不加载任何 DOM 或浏览器 API,因此大大缩短了 Jest 的启动时间。
¥react-native
ships with a Jest preset, so the jest.preset
field of your package.json
should point to react-native
. The preset is a node environment that mimics the environment of a React Native app. Because it doesn't load any DOM or browser APIs, it greatly improves Jest's startup time.
定制 transformIgnorePatterns
¥transformIgnorePatterns customization
transformIgnorePatterns
选项可用于指定 Babel 应转换哪些文件。遗憾的是,许多 react-native
npm 模块在发布之前不会预编译其源代码。
¥The transformIgnorePatterns
option can be used to specify which files shall be transformed by Babel. Many react-native
npm modules unfortunately don't pre-compile their source code before publishing.
默认情况下,jest-react-native
预设仅处理项目自己的源文件和 react-native
。如果你有必须转换的 npm 依赖,你可以通过包含 react-native
以外的模块来自定义此配置选项,方法是对它们进行分组并使用 |
运算符将它们分开:
¥By default the jest-react-native
preset only processes the project's own source files and react-native
. If you have npm dependencies that have to be transformed you can customize this configuration option by including modules other than react-native
by grouping them and separating them with the |
operator:
{
"transformIgnorePatterns": [
"node_modules/(?!(react-native|my-project|react-native-button)/)"
]
}
你可以使用工具 像这样 测试哪些路径会匹配(从而从转换中排除)。
¥You can test which paths would match (and thus be excluded from transformation) with a tool like this.
如果路径与提供的任何模式匹配,transformIgnorePatterns
将从转换中排除文件。因此,如果你不小心,分成多个模式可能会产生意想不到的结果。在下面的示例中,foo
和 bar
的排除(也称为否定先行断言)相互抵消:
¥transformIgnorePatterns
will exclude a file from transformation if the path matches against any pattern provided. Splitting into multiple patterns could therefore have unintended results if you are not careful. In the example below, the exclusion (also known as a negative lookahead assertion) for foo
and bar
cancel each other out:
{
"transformIgnorePatterns": ["node_modules/(?!foo/)", "node_modules/(?!bar/)"] // not what you want
}
setupFiles
如果你想为每个测试文件提供附加配置,可以使用 setupFiles
配置选项 来指定安装脚本。
¥If you'd like to provide additional configuration for every test file, the setupFiles
configuration option can be used to specify setup scripts.
moduleNameMapper
moduleNameMapper
可用于将模块路径映射到不同的模块。默认情况下,预设将所有图片映射到图片存根模块,但如果找不到模块,此配置选项可以提供帮助:
¥The moduleNameMapper
can be used to map a module path to a different module. By default the preset maps all images to an image stub module but if a module cannot be found this configuration option can help:
{
"moduleNameMapper": {
"my-module.js": "<rootDir>/path/to/my-module.js"
}
}
提示
¥Tips
使用 jest.mock 模拟原生模块
¥Mock native modules using jest.mock
react-native
中内置的 Jest 预设附带了一些应用于 React-Native 存储库的默认模拟。然而,一些 react-native 组件或第三方组件依赖于原生代码来渲染。在这种情况下,Jest 的手动模拟系统可以帮助模拟底层实现。
¥The Jest preset built into react-native
comes with a few default mocks that are applied on a react-native repository. However, some react-native components or third party components rely on native code to be rendered. In such cases, Jest's manual mocking system can help to mock out the underlying implementation.
例如,如果你的代码依赖于名为 react-native-video
的第三方原生视频组件,你可能需要使用如下手动模拟将其存根:
¥For example, if your code depends on a third party native video component called react-native-video
you might want to stub it out with a manual mock like this:
jest.mock('react-native-video', () => 'Video');
这会将组件渲染为 <Video {...props} />
,并在快照输出中显示其所有属性。另见 有关 Enzyme 和 React 16 的注意事项。
¥This will render the component as <Video {...props} />
with all of its props in the snapshot output. See also caveats around Enzyme and React 16.
有时你需要提供更复杂的手动模拟。例如,如果你想将原生组件的 prop 类型或静态字段转发到模拟,你可以通过 jest-react-native 的这个辅助程序从模拟返回不同的 React 组件:
¥Sometimes you need to provide a more complex manual mock. For example if you'd like to forward the prop types or static fields of a native component to a mock, you can return a different React component from a mock through this helper from jest-react-native:
jest.mock('path/to/MyNativeComponent', () => {
const mockComponent = require('react-native/jest/mockComponent');
return mockComponent('path/to/MyNativeComponent');
});
或者,如果你想创建自己的手动模拟,你可以执行以下操作:
¥Or if you'd like to create your own manual mock, you can do something like this:
jest.mock('Text', () => {
const RealComponent = jest.requireActual('Text');
const React = require('react');
class Text extends React.Component {
render() {
return React.createElement('Text', this.props, this.props.children);
}
}
Text.propTypes = RealComponent.propTypes;
return Text;
});
在其他情况下,你可能想要模拟不是 React 组件的原生模块。可以应用相同的技术。我们建议在真实设备上运行 React Native 应用时检查原生模块的源代码并记录模块,然后在真实模块之后对手动模拟进行建模。
¥In other cases you may want to mock a native module that isn't a React component. The same technique can be applied. We recommend inspecting the native module's source code and logging the module when running a react native app on a real device and then modeling a manual mock after the real module.
如果你最终一遍又一遍地模拟相同的模块,建议在单独的文件中定义这些模拟并将其添加到 setupFiles
列表中。
¥If you end up mocking the same modules over and over it is recommended to define these mocks in a separate file and add it to the list of setupFiles
.