Androidのローカルテストで、フレームワーク依存のコードのテストを書く際にはみなさん、Robolectricを使っているかと思います。
今回はActivityやViewに対するBackボタン=戻るボタン処理の挙動をテストする方法をメモります。
大した内容じゃないですが、再度調べなくて良いように。
なぜ戻るボタンのテストをしたいか?
普通にライフサイクルのテストとかするなら、activity.pause()やactivity.finish()などを呼ぶのがいいと思います。
今回テストしたかったのは、以下の動作確認がしたかったからです。
- Activity内のViewが特定条件で戻るボタンをconsumeするので、条件を変えた際の戻るボタンのイベント伝播を確認したい。
- Activityが戻るボタンを受けとった際の処理をテストしたい。
- onBackPressedがコールされること。
- 特定viewに対するkeyEventを送信することでテストを記述したい。
- 手動で呼べば再現できるけど、なるべくフレームワークの実装通りに伝播させたい。
戻るボタンイベントの渡し方
結論ですが、以下のようにKeyEventをdispatchすることで、Viewを通してActivityにイベントを伝播させることができました。
@Test
fun バックボタンを押すとActivityが閉じる() {
// onBackPressedは戻るボタンのUp時に発火される。
val keyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK)
// ただし、KeyEvent自体が該当Viewに対してDownされてTracking状態になっていないと発火しない。
// そのため、trackingを監視するクラスにDownイベントを渡して開始させて、Upイベントにフラグを立たせる。
val state = KeyEvent.DispatcherState()
state.startTracking(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK), null)
state.handleUpEvent(keyEvent)
// KeyEventがisTrackingかつisCanceledじゃない時、onBackPressedが呼ばれる。
println("event: ${keyEvent} ${keyEvent.isTracking()}, ${keyEvent.isCanceled()}")
// イベントは伝播して誰かにconsumeされる
val consumed = view?.dispatchKeyEvent(keyEvent)
assertThat(consumed).isTrue()
// onBackPressedが呼ばれてActivityがfinishしている。
assertThat(activity.get().isFinishing).isTrue()
}
KeyEventの伝播の仕方は次のような順番でした
- Focusの当たったView
- Parent ViewGroupをたどる(行き着くまで)
- DecorView
- PhoneWindow
- Window.Callback(Activityが継承してWindowに登録している)
ActivityとWindowクラスの関係がイマイチ理解できていないですが、Activityで受け取るタイミングは後ろの方でした。
参考
AOSPのActivityやKeyEventのコードを見ればわかることでした。
Activity.java
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (getApplicationInfo().targetSdkVersion
>= Build.VERSION_CODES.ECLAIR) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking()
&& !event.isCanceled()) {
onBackPressed();
return true;
}
}
return false;
}
KeyEvent.java
public void startTracking(KeyEvent event, Object target) {
if (event.getAction() != ACTION_DOWN) {
throw new IllegalArgumentException(
"Can only start tracking on a down event");
}
if (DEBUG) Log.v(TAG, "Start trackingt in " + target + ": " + this);
mDownKeyCode = event.getKeyCode();
mDownTarget = target;
}
ちょっと行き着くのに時間かかってしまったのでメモしておきます。