Edited at

React Nativeのテストの話


自己紹介


  • 筑井 友啓 Tsukui Tomohiro

  • ecbo株式会社(2018/4 ~ )

  • 「ecbo cloak」というアプリをリリースしました

  • twitter @two2q


image.png



Goal

明日にでもjest使ってくれたら嬉しいです



jestいいよ



かなりDXが高く、すぐにTDD出来る



jest とは


  • config いらずで

  • snapshotテストもできて

  • 平行実行可能で

  • 多くのAPIを提供している
    image.png



react-nativeならjestすぐに使えるよ

react-nativeのバージョンが0.38以上であれば、react-native init 時にjestがセットアップされています。


package.json

  "scripts": {

"test": "jest"
},
"jest": {
"preset": "react-native"
}



jestすぐに使えるよ

$ yarn test

参考 https://jestjs.io/docs/ja/tutorial-react-native



no configとはいえ



なんだかんだ設定は必要な時もある



eslint 使っている場合

eslintエラーが出ているのでeslint-plugin-jestを追加してください。

$ yarn add --dev eslint eslint-plugin-jest

{

"plugins": ["jest"]
}



TypeScript

ts-jestのインストールがてっとり早い



jestの基本



methodは標準的なものを取り揃えています

image.png



describe(name, fn) と test(name, fn, timeout)

describe: いくつかのテストをまとめるブロックを作ります。

test: 実際にテストを書くブロックです。期待値との比較などを行います。it(name, fn, timeout) も同じです。

const myBeverage = {

delicious: true,
sour: false,
};

describe('my beverage', () => {
test('is delicious', () => {
expect(myBeverage.delicious).toBeTruthy();
});

test('is not sour', () => {
expect(myBeverage.sour).toBeFalsy();
});
});



非同期のテストについての注意

非同期関数をテストする際には、 return をしてあげないと絶対に success になってしまう。

// targetFn.js

const targetFn = async () => { } // do something

// targetFn.spec.js
test('test async', () => {
return targetFn().then((result) => {
expect(result).toBe('expected')
})
}

他にもasync () => { await ... } や (done) => { ... done() }

でもテスト可能です。

参考: https://jestjs.io/docs/en/tutorial-async



おすすめなMethod



describe.each(table)(name, fn, timeout)

同じtestを複数のデータセットで回したい時に利用します。

describe.each`

a | b | expected
${1} | ${1} | ${2}
${1} | ${2} | ${3}
${2} | ${1} | ${3}
`
('$a + $b', ({a, b, expected}) => {
test(`returns ${expected}`, () => {
expect(a + b).toBe(expected);
});

test(`returned value not be greater than ${expected}`, () => {
expect(a + b).not.toBeGreaterThan(expected);
});

test(`returned value not be less than ${expected}`, () => {
expect(a + b).not.toBeLessThan(expected);
});
});



最近書いたコード

const baseDateString = '2019-02-26T18:30:00.000';

describe.each`
language | expected
${'en'} | ${'02/26 6:30 PM'}
${'ja'} | ${'02/26 18:30'}
${'zh-Hant'} | ${'02/26 18:30'}
`
('localizedDateTime with $language', ({ language, expected }) => {
it(`should return ${expected}`, () => {
i18n.changeMomentLocale(language);
const result = dateTimeExpression.localizedDateTime(moment(baseDateString));
expect(result).toEqual(expected);
});
});

見通しよく、データセットの追加も容易に!

参照: https://jestjs.io/docs/en/api#describeeachtable-name-fn-timeout



おすすめな実行オプション その1



--watch



--watch

変更されたファイルを検知して、関連するテストを実行してくれます。

個人的にメインな使い方です。高いDXTDDを実現してくれます。



一度実行すればjestがファイルを監視してくれる

image.png



おすすめな使い方


  1. 先にテスト対象のファイルの [spec|test].js を用意し、ある程度describeも書いておきます。

  2. エディタのterminalで $ jest --watch します。

  3. 全てのテストがerrorになります。

  4. テストがパスするまで実装してください。



主な利用シーン


  • ducks層の実装

  • form validationの実装

  • utils系の実装

  • Component内の複雑なfunctionの実装

=> ピュアなjsでいける部分については基本的にjestで先に仕様をまとめてから実装します。



おすすめな実行オプション その2



--coverage



jest --coverage

カバレッジを計測してくれます。ソース全体のカバレッジではなく、あくまでもjestで実行されたファイルのみを計測します。

カバレッジが重要ではないのですが、no configで実行出来るという点がかなり素敵かな、と思っています。



実行してみた

image.png



おすすめな使い方

悦に浸ったり、githubにバッジをつけてみてください。jest-coverage-badges



API通信部分のテスト



nockでサーバーのモックを作る

$ yarn add nock

指定したリクエストを上書きしてモックデータを返却してくれます。一度レスポンスの型を決めてしまえばAPI通信部分のテストをサーバーなしで実行出来るようになります。



具体的には

import nock from 'nock';

nock('http://www.example.com')
.get('/hogehoge')
.reply(200, { foo: 'bar' })

こうする事で、http://www.example.comへのrequestが全てinterceptされるようになり、/hogehogeへのgetリクエストにはstatus200と共に {foo: 'bar'} が返却されるということを意味します。

なお、jestにも標準でmockが用意されているのですが、モジュールのモックになってしまうのでダミーデータを返却するクライアントのモックを用意する必要があります。その点nockは一手間減るという印象があります。



snapshot テストについて

初回snapshotテスト実行時に.snap形式でスナップショットを生成し、以降のテストではそのスナップショットとコンポーネントを比較して変更があればエラーとして検知するものです。

import renderer from "react-test-renderer";

import App from "../App";

// 省略

it("renders the loading screen", async () => {
const tree = renderer.create(<App />).toJSON();
expect(tree).toMatchSnapshot();
});



コンポーネントの品質についてはstorybookの方が今の所効果あります


  • そもそも変更が多いのでsnapshotの恩恵が少ない(影響範囲がわかるのはよかった)

  • 実際に操作をさせないとバグかどうかの判断がつきにくい


  • @storybook/addon-knobsを整備してあげてデザイナーさんに触ってもらった方が結局早い



TODO E2Eテスト



こんな感じで、色々jestのキャッチアップをしながらコードを書いています。



なぜ書くのか



ひどいデグレは防げるかもしれない


  • 障害対応で疲弊する事が減るかもしれない

  • 修正に対する恐怖が減る、下手なオンボーディングよりも安心する



過去の自分が書いた負債で後任が爆死しない


  • specさえあれば負債の負債度が減る

  • 恨みを買わなくて済むので平和になる

  • ここクソだけどspecあるからひとまず安心する、とか言われる



わざわざ端末触らなくても実装出来る


  • 端末の起動よりもjestの方がはるかに早いので少し早く帰れる

  • 端末をフリフリしてどっかに飛ばすリスクが減る



思い切ったリファクタも結構出来る


  • もちろん程度にもよるが、ないよりははるかにマシ



コードレビューのフィードバック対応も早い


  • 指摘内容はそれに対応するspecを添えればレビュアーにとってもわかりやすい

  • フィードバック修正後のテスト工数の削減



基本的にいい事しかないし、健全な働きが出来る



テストは福利厚生と言ってもいいのではないだろうか?



ひとまず福利厚生に書いてみた

image.png

Wantedlyより抜粋



一緒に世界中の人に使ってもらえるアプリを作りませんか?