メインコンテンツへスキップ
Version: 27.1

React Nativeアプリをテスト

Facebookでは、Jestを使用して React Native アプリケーションをテストします。

動く React Native アプリケーションのテストに関するより深い知見は以下のシリーズを読むことで得てください: Part 1: Jest – Snapshot come into playPart 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"
}
}

Note: もしあなたが react-native アプリケーションをアップグレードする際に jest-react-native プリセットを利用していた場合はその依存を package.json から削除し、 プリセットを react-native に変更してください。

yarn test を実行するだけで、Jest でテストを実行できます。

スナップショットテスト#

さあ スナップショットテスト をいくつかのビューとテキストコンポーネントをもつサンプルコンポーネントに対して作りましょう。

// Intro.js
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のスナップショット機能を利用しましょう:

// __tests__/Intro-test.js
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を実行すると、このようなファイルが出力されます:

// __tests__/__snapshots__/Intro-test.js.snap
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.jsonjest.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 リストに追加することをおすすめします。