Open8

React Nativeのテストコードを書くときのTips集

meijinmeijin

使っているテストフレームワーク

  • jest
    • jest-expo
    • ts-jest
    • babel-jest
    • @types/jest
    • @testing-library/jest-native
  • @testing-library/react-native

前提

  • expoを使っている(eject予定なので、そのときの移行Tipsもまとめられたらと思う)
meijinmeijin

jest.config.js

module.exports = {
  preset: 'jest-expo',
  setupFilesAfterEnv: ['@testing-library/jest-native/extend-expect'],
  moduleFileExtensions: ['ts', 'tsx', 'js'],
  transform: {
    '^.+\\.(js)$': '<rootDir>/node_modules/babel-jest',
    '\\.(ts)$': 'ts-jest',
    '^.+\\.tsx?$': 'babel-jest',
  },
  testRegex: '(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$',
  testPathIgnorePatterns: ['\\.snap$', '<rootDir>/node_modules/'],
  cacheDirectory: '.jest/cache',
  globals: {
    'ts-jest': {
      tsconfig: 'tsconfig.json',
    },
  },
  transformIgnorePatterns: [
    'node_modules/(?!((jest-)?react-native|react-router-native|react-clone-referenced-element|expo(nent)?|@expo(nent)?/.*|react-navigation|以下略))',
  ],
};

ポイントはtransformIgnorePatterns
ここに地道に設定を足していく

あとは設定のなかでいくつかDeprecatedになるものがあり、そのへんのネット文献だと警告が出るものは修正している

Kyosuke KuboKyosuke Kubo

良いスクラップ記載ありがとうございます!
globalsにts-jestを記載しているとdeprecatedの警告が出るので、
"\\.(ts)$": ["ts-jest", { tsconfig: "tsconfig.json" }],この書き方の方が良いかもしれません。

meijinmeijin

各jest系パッケージのバージョン

メジャーバージョンを合わせておくほうがよさげ。

meijinmeijin

テストファイルの拡張子

基礎的なところだが、テストファイルの拡張子は.test.tsxのようにtsxとする

meijinmeijin

コンポーネントの表示テスト

以下が例。

      const component = render(<ChatMessage message={createMockMessage()} />);
      expect(component.queryByText('text')).toBeTruthy();
      expect(component.queryByText(LABELS.READ)).toBeNull();
      expect(component.queryByA11yLabel(LABELS.DELETE_BUTTON)).toBeNull();

render

TSXコンポーネントを渡す。

queryByText

実際に表示されているテキストを検索してくれる。厳密にテストしたければもう少し工夫の余地がありそう。

ちなみにgetByTextというのもありこちらを利用しても同様の結果は得られる。しかし「表示されていないこと」をテストしたいときgetXXXを使うとそこで落ちてしまうため、現状queryをベースにテストを書いていくことが好み。

queryByA11yLabel

画像など、表示されている内容がテキストではない場合にとりあえず使っている。
元コンポーネントの方でアクセシビリティラベルを設定するとよい。

          <Image
            accessibilityLabel="送信された画像"
            source={{ uri: props.uri }}
          />

存在しない場合はNULLが返ってくるためそれをアサートする

meijinmeijin

@testing-library/react-native 注意点

cleanup

以下を書く

afterEach(cleanup);

render

renderメソッドはitメソッド内で実行する必要がありそう。describeで実行するとUnable to find node on an unmounted component.というエラーになった

meijinmeijin

Alertのモック

アラートが表示されることをモックでアサートしたい

spyOnでモック関数をImplementationOnceして入れ込めばいい。引数はそのまま流す

import { Alert } from 'react-native';
const mockAlert = jest.fn();
jest.spyOn(Alert, 'alert').mockImplementationOnce((...arg) => mockAlert(...arg));

そしてテストは普通にモック関数のテストを書けばいい。以下は若干好奇心もあり書きすぎたがだいたいこんな感じで書ける

    fireEvent(component.queryByA11yLabel('削除ボタン'), 'click');
    expect(Alert.alert).toHaveBeenCalledTimes(1);
    expect(mockAlert.mock.calls.length).toBe(1);
    expect(mockAlert.mock.calls[0][0]).toBe('本当に削除しますか?');
    expect(mockAlert.mock.calls[0][1]).toBe('この操作は取り消すことができません');
    expect(mockAlert.mock.calls[0][2][0].text).toBe('いいえ');
    expect(mockAlert.mock.calls[0][2][1].text).toBe('はい');