@testing-library/react-native でスナップショットテスト
React Native でテストを実装するためのパッケージ @testing-library/react-native
の紹介です。
このパッケージを使うことで React Native のコンポーネントのテストを実装でき、UIの変更に対してスナップショットテストを実装することも可能です。
React Native でのスナップショットテストのやり方はいくつかあります。 私は React Native Web とStorybook を組み合わせて、Storybook のアドオンで提供されている StoryShot を使っていました。
設定がややこしかったり、そもそも React Native Web を動かすのでちょっと勝手の違う部分があります。Storybook でUIコンポーネントを確認できるのはとても便利ですが。 Webアプリも同時に作っているなどであれば、恩恵をフルで受け取ることができるのでおすすめですが、私はスマホアプリのみだったのでちょっと大袈裟に感じていました。
他のテスト用パッケージを探してみたところ、@testing-library/react-native
を見つけたのでこれを使うことにしました。
結論、快適にやりたかったことができたのでとても便利です。導入も簡単ですし、jest と組み合わせて動かせるので他環境のtest とほぼ同様に書くことができます。
概要
- パッケージ
- インストール
- 設定
- テストケースの用意
- スナップショットの上書き
- Provider が必要なコンポーネントを含む場合
パッケージ
https://callstack.github.io/react-native-testing-library/
https://testing-library.com/docs/react-native-testing-library/intro
インストール
いつも通り npm・yarn でインストールします。テストにのみ利用するパッケージなのでdev環境へのインストールになります。
npm install --save-dev @testing-library/react-native
設定
とくに設定は必要ありませんでした。
Expo を使っている場合に関連するパッケージのWarnなどが出ている場合、jest 設定ファイル jest.config.js
の preset を以下のように変更すると解決できました。
module.exports = {
preset: "jest-expo",
}
テストケースの用意
例えば以下のように描きます。render メソッドで対象のコンポーネントをシミュレートでき、それに対して JSON に変換して Snapshot を撮影したり、タップイベントを発生させたりできます。
import { render, fireEvent } from "@testing-library/react-native";
~~~
import { CustomComponent } from "../../../../src/components/CustomComponent";
describe("CustomComponent", () => {
it(`CustomComponentを描写`, () => {
const component = render(<CustomComponent />);
expect(component.toJSON()).toMatchSnapshot();
});
it(`ボタンをタップしたとき、pnPressメソッドを実行する`, () => {
const onPressMock = jest.fn();
const component = render(
<CustomComponent onPress={onPressMock} />
);
const button = component.getByTestId("customComponentButton");
fireEvent.press(button);
expect(onPressMock).toBeCalled();
});
});
スナップショットの作成のみでなく、fireEvent
を使うことで要素のタップ動作のテストも行うことができます。
タップさせたりする際に要素を指定する必要があります。その場合は要素の「testID」propで検索するgetByTestId
や、「getByText
などがあり、それらで探します。
原則的には「実装に近い形でテストが用意できるほど良い」というような考え方があるので、テストのためだけにtestID を設定するのは避けるべきとされているようです。 しかし、Detoxなどend-to-end テストのツールなどでも利用できることを考慮すると、積極的に使ってもいいと考えています。実装の際にprop が1つ増えるのですが、テスト描きやすくなるので個人的にはtestIDを積極的に使う方針にしています。
スナップショットの上書き
初回にテストを実行した場合、スナップショットのファイルが生成されます。 次回以降はスナップショットに差分があればtest が失敗するようになります。
テストを行いスナップショットの差分を確認して、意図した内容であれば --updateSnapshot
のオプションを指定してjestを実行することでスナップショットの上書きを行うことができます。
Provider が必要なコンポーネントを含む場合
react-native-paper
など、親コンポーネントにProvider を設定しておく必要があるコンポーネントの場合、そのままrender するとエラーになってしまいます。
その場合は、以下のようにカスタムrender を定義したファイルtest-util.js
を用意して、そちらのrender を利用できるようにするとテストを動かすことが可能です。
https://testing-library.com/docs/react-native-testing-library/setup
import { render } from "@testing-library/react-native";
import { Provider as PaperProvider } from "react-native-paper";
const AllTheProviders = ({ children }) => {
return <PaperProvider>{children}</PaperProvider>;
};
const customRender = (ui, options) =>
render(ui, { wrapper: AllTheProviders, ...options });
// re-export everything
export * from "@testing-library/react-native";
// override render method
export { customRender as render };
@testing-library/react-native
ではなく、test-util.js
から render をimport すれば、Provider が必要なコンポーネントもテストできます。
ちなみに、jest.config.js
に以下の記述を追加することで、test-util
をパスで指定しなくてもimport { render } from 'test-utils';
で指定できます。
カスタムrender を用意する場合は便利なので対応しておくと良いです。
module.exports = {
moduleDirectories: [
'node_modules',
'utils', // a utility folder
__dirname, // the root directory
],
}
おわりに
@testing-library/react-native
を使うことで簡単にコンポーネントのテストを用意できます。
testing-library としてまとまった仕様になっており、jest を使ったテストケースの書き方なのでわかりやすく、React など他の環境でも同様の書き方になるので応用できます。
単純なユニットテストを行う分には十分なAPIが用意されているので、快適にテストを行うことができ開発の手助けになること間違いないです。