Testing React Native Apps
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 ファイルに追加されます。
// package.json "scripts": { "test": "jest" }, "jest": { "preset": "react-native" }
Note: もしあなたが react-native アプリケーションをアップグレードする際に jest-react-native
プリセットを利用していた場合はその依存を package.json
から削除し、 プリセットを react-native
に変更してください。
シンプルに npm test
を実行するだけで Jest でテストを実行することができます。
スナップショットテスト #
さあ スナップショットテスト をいくつかのビューとテキストコンポーネントをもつサンプルコンポーネントに対して作りましょう。
// Intro.js import React, {Component} from 'react'; import { StyleSheet, Text, View, } from 'react-native'; 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 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> ); } }
コンポーネントとのやり取りとレンダリングされた出力をキャプチャしてスナップショットファイルを作成するために、ReactのテストレンダラーとJestのスナップショット機能を利用しましょう:
// __tests__/Intro-test.js import 'react-native'; import React from 'react'; import Intro from '../Intro'; // Note: test renderer は react-native の後で import される必要があります。 import renderer from 'react-test-renderer'; test('renders correctly', () => { const tree = renderer.create( <Intro /> ).toJSON(); expect(tree).toMatchSnapshot(); });
npm 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を実行して既存のスナップショットを上書きします。
この例のコードは examples/react-native で参照できます。
プリセット設定 #
プリセットは環境をセットアップしますが、それはとても偏っており Facebook において有用であるとわかったものをベースにしています。 ただ、すべての設定値は上書き可能でプリセットを使ってない場合でもそれに合わせて設定することができます。
環境 #
react-native
は Jest プリセットとともに提供されているので、 package.json
の jest.preset
フィールドにはreact-native
が指定されているべきです。 プリセットは node 環境であり React Native アプリケーション環境を模倣します。そのため一切の DOM や ブラウザ API をロードしないため、Jest の起動時間を大きく向上させます。
transformIgnorePatterns のカスタム #
transformIgnorePatterns
設定値を使うと babel で変換されるファイルをホワイトリストやブラックリストとして管理することができます。 残念なことに多くの react-native npm モジュールは公開前にソースコードをコンパイルしていません。
デフォルトでは jest-react-native プリセットは自身のプロジェクトのソースファイルと react-native のみ処理します。 もしあなたが変換が必要な npm の依存をもっている場合、この設定値を使って react-native に加えてその依存をホワイトリストとして設定することができます。
"transformIgnorePatterns": [ "node_modules/(?!(react-native|my-project|react-native-button)/)" ]
setupFiles #
もしあなたが各テストファイルに対して追加設定を行いたい場合は、setupFiles
設定値 を使ってセットアップスクリプトを指定することが利用できます。
moduleNameMapper #
moduleNameMapper
を使うとあるモジュールのパスを別のモジュールにマップすることができます。 デフォルトではプリセットはすべてのイメージをイメージスタブモジュールにマップしていますがもしモジュールが見つからない場合はこの設定値が役立つかもしれません。
"moduleNameMapper": { "my-module.js": "<rootDir>/path/to/my-module.js" }
Tips #
jest.mock を使ってネイティブモジュールをモックする #
react-native
に組み込まれている Jest プリセットはいくつかのデフォルトモックを持っており、それらは react-native レポジトリにも適用されています。 しかしながらいくつかの react-native コンポーネントやサードパーティーコンポーネントはネイティブコードに依存して描画されています。 そのような場合には、Jest のマニュアルモックシステムを使って内部の実装部分をモックすることで問題を回避できる可能性があります。
例えば、もしあなたのコードが react-native-video
という名前のビデオコンポーネントに依存していた場合、あなたはそれをモック関数で置き換えたくなるかもしれません。
jest.mock('react-native-video', () => 'Video');
これはコンポーネントをそのコンポーネントのすべての props とともに <Video {...props} />
としてスナップショット出力の中で描画します。
ときにより複雑なマニュアルモックを必要とする場合もあるでしょう。 例えば、もしあなたがネイティブコンポーネントの 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 = require.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
リストに追加することをおすすめします。
react-native は test renderer の前に import される必要がある #
現時点では react-native は test renderer がロードされる前に import されている必要があります。
import 'react-native'; // react-native の後に import される必要があります import renderer from 'react-test-renderer';
@providesModule
#
もしあなたが Facebook の @providesModule
モジュールシステムを npm パッケージを通して使いたい場合は、デフォルトの haste 設定を上書きする必要があり、npm モジュールを providesModuleNodeModules
に追加しなければなりません。
"haste": { "defaultPlatform": "ios", "platforms": ["android", "ios"], "providesModuleNodeModules": [ "react", "react-native", "my-awesome-module", "my-text-component" ] },
もしあなたが違うデフォルトプラットフォームでテストしたい場合や、別のプラットフォームに対してビルドしている場合は、defaultPlatform
もしくは platforms
を変更することができます。