シェアフル Advent Calendar 2018 17日目の記事です。
ある程度アプリの機能が揃ってきたので、そろそろテスト追加フェーズかなという感じでe2eの実装してみた
react-nativeでe2eの実装
react-nativeではDetoxを使ってe2eを実装していきます
また↓を使えばexpoでもe2eが可能です
GitHub - expo/detox-expo-helpers
detox-tools/packages/expo-detox-hook at master · expo/detox-tools · GitHub
Detox実装
事前準備
ここにある通りでOK
Detox/Introduction.GettingStarted.md at master · wix/Detox · GitHub
DEMOを動かしてみよう
こちらが、動かせるサンプルです
READMEの通りにセットアップすれば、こんな感じに動きます
早速アプリ組み込んでみる
インストール & 設定
mochaとjestどちらでも使えますが、ここではDEMOと同じでmochaを選択
npm i -D detox detox-expo-helpers expo-detox-hook mocha
初期テストコードをcliから追加
npm install -g detox-cli detox init -r mocha
package.jsonにdetoxの設定を追加
"detox": {
"configurations": {
"ios.sim": {
"binaryPath": "bin/Exponent.app",
"type": "ios.simulator",
"name": "iPhone 7"
}
}
}
package.jsonにe2eのコマンドを追加
"scripts": {
"e2e": "detox test --configuration ios.sim"
これで初期設定はOK
テストコード
テストの書き方
アプリ側に 「testID」を埋め込んでテストコード側で、そこを対象に操作していきます
テキストの判定
■アプリ
<View> <Text>プランの登録はありません</Text> </View>
■テストコード
await expect(element(by.label("プランの登録はありません"))).toBeVisible();
ボタンをタップする
■アプリ
<TouchableOpacity onPress={this.props.onCreate} testID="addSchedule">
<Ionicons name="ios-add-circle" size={80} color="#4DB6AC" />
</TouchableOpacity>
■テストコード
await element(by.id("addSchedule")).tap();
テキストを入力する
■アプリ
<TextInput
placeholder="タイトルを入力"
placeholderTextColor="#ffffff"
onChangeText={title => this.setState({ title })}
defaultValue={this.props.title}
testID="inputTextScheduleDetailTitle"
/>
■テストコード
await element(by.id("inputTextScheduleDetailTitle")).replaceText("新宿駅");
大体、これくらい覚えておけばOK
実装
スケジュール登録を一通り操作してスクリーンショットを取る形式で実装したら、こんな感じになった
■ShioriNative/e2e/firstTest.spec.js
const { reloadApp } = require("detox-expo-helpers");
const { takeScreenshot } = require("./helpers");
describe("Example", () => {
beforeEach(async () => {
await reloadApp();
});
afterEach(async () => {
takeScreenshot();
});
it("初期表示", async () => {
await expect(element(by.label("プランの登録はありません"))).toBeVisible();
});
it("スケジュール追加", async () => {
await element(by.id("addSchedule")).tap();
await element(by.id("inputTextTitle")).tap();
await element(by.id("inputTextTitle")).replaceText("葛西臨海公園");
await element(by.id("completion")).tap();
takeScreenshot();
await element(by.id("addScheduleDetail")).tap();
takeScreenshot();
await element(by.id("inputTextScheduleDetailTitle")).tap();
await element(by.id("inputTextScheduleDetailTitle")).replaceText("新宿駅");
await element(by.id("inputTextScheduleDetailMemo")).tap();
await element(by.id("inputTextScheduleDetailMemo")).replaceText(
"8:00に西口に集合する"
);
takeScreenshot();
await element(by.id("saveScheduleDetail")).tap();
await element(by.id("addScheduleDetail")).tap();
await element(by.id("inputTextScheduleDetailTitle")).tap();
await element(by.id("inputTextScheduleDetailTitle")).replaceText(
"葛西臨海公園"
);
await element(by.id("inputTextScheduleDetailMemo")).tap();
await element(by.id("inputTextScheduleDetailMemo")).replaceText(
"行く場所:砂浜、観覧車、水族園"
);
await element(by.id("saveScheduleDetail")).tap();
await element(by.id("addScheduleDetail")).tap();
await element(by.id("inputTextScheduleDetailTitle")).tap();
await element(by.id("inputTextScheduleDetailTitle")).replaceText(
"葛西臨海公園水上バス"
);
await element(by.id("saveScheduleDetail")).tap();
await element(by.id("addScheduleDetail")).tap();
await element(by.id("inputTextScheduleDetailTitle")).tap();
await element(by.id("inputTextScheduleDetailTitle")).replaceText(
"浅草寺二天門前"
);
await element(by.id("saveScheduleDetail")).tap();
takeScreenshot();
await element(by.id("saveSchedule")).tap();
});
});
e2e実行
以下のコマンドで実行される
npm start
npmrun e2e
こんな感じで動作します

実装してみた感想
ポジティブ
- 予想以上に手頃に実装できる
- 「testID」はキモいと思っていたが、完全にテスト用と割り切ってしまえば意外と気にならない
- スタイル崩れチェックぐらいならスクリーンショットのチェックだけでもOKかも
ネガティブ
ciで実行できるのかな?その辺が、まだよく分かってなので、もう少し調べる予定
