React Nativeアプリをテスト
Facebookでは、Jestを使用して React Native アプリケーションをテストします。
動く React Native アプリケーションのテストに関するより深い知見は以下のシリーズを読むことで得てください: Part 1: Jest – Snapshot come into play、Part 2: Jest – Redux Snapshots for your Actions and Reducers
セットアップ
react-native 0.38 より react-native init
コマンドに Jest セットアップがデフォルトで含まれています。 以下の設定が自動的に package.json ファイルに追加されます。
{
"scripts": {
"test": "jest"
},
"jest": {
"preset": "react-native"
}
}
yarn test
を実行するだけで、Jest でテストを実行できます。
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._
スナップショットテスト
さあ スナップショットテスト をいくつかのビューとテキストコンポーネントをもつサンプルコンポーネントに対して作りましょう。
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のスナップショット機能を利用しましょう:
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
を実行すると、このようなファイルが出力されます:
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 code for this example is available at examples/react-native.
プリセット設定
プリセットは環境をセットアップしますが、それはとても偏っており Facebook において有用であるとわかったものをベースにしています。 ただ、すべての設定値は上書き可能でプリセットを使ってない場合でもそれに合わせて設定することができます。
環境
react-native
は Jest プリセットとともに提供されているので、 package.json
の jest.preset
フィールドにはreact-native
が指定されているべきです。 プリセットは node 環境であり React Native アプリケーション環境を模倣します。 そのため一切の DOM や ブラウザ API をロードしないため、Jest の起動時間を大きく向上させます。
transformIgnorePatterns のカスタム
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.
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
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
設定値 を使ってセットアップスクリプトを指定することが利用できます。
moduleNameMapper
moduleNameMapper
を使うとあるモジュールのパスを別のモジュールにマップすることができます。 デフォルトではプリセットはすべてのイメージをイメージスタブモジュールにマップしていますがもしモジュールが見つからない場合はこの設定値が役立つかもしれません。
{
"moduleNameMapper": {
"my-module.js": "<rootDir>/path/to/my-module.js"
}
}
Tips
jest.mock を使ってネイティブモジュールをモックする
react-native
に組み込まれている Jest プリセットはいくつかのデフォルトモックを持っており、それらは react-native レポジトリにも適用されています。 However, some react-native components or third party components rely on native code to be rendered. そのような場合には、Jest のマニュアルモックシステムを使って内部の実装部分をモックすることで問題を回避できる可能性があります。
例えば、もしあなたのコードが react-native-video
という名前のビデオコンポーネントに依存していた場合、あなたはそれをモック関数で置き換えたくなるかもしれません。
jest.mock('react-native-video', () => 'Video');
これはコンポーネントをそのコンポーネントのすべての props とともに <Video {...props} />
としてスナップショット出力の中で描画します。 Enzyme と React 16 についての注意事項も参照してください。
ときにより複雑なマニュアルモックを必要とする場合もあるでしょう。 例えば、もしあなたがネイティブコンポーネントの prop types もしくは静的フィールドをモックに渡したい場合に、jest-react-native のヘルパーを使って別の React モックコンポーネントを返すことができます。
jest.mock('path/to/MyNativeComponent', () => {
const mockComponent = require('react-native/jest/mockComponent');
return mockComponent('path/to/MyNativeComponent');
});
もしくは、あなたがマニュアルモックを作りたい場合は以下のようにして作ることができます。
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 コンポーネントでないネイティブモジュールをモックしたいケースがあります。 そのケースでも同じテクニックが使えます。 ネイティブモジュールのソースコードをよく調べて、実デバイス上でアプリを実際に動かしながらモジュールのログを取って、マニュアルモックを設計することをおすすめします。
もしあなたが同じモジュールを何度もモックすることになったら、そのモックは別ファイルに定義してそれを setupFiles
リストに追加することをおすすめします。