お茶漬けびより

"あなたに教わったことを、噛んでいるのですよ" 五等分の花嫁 7巻 「最後の試験が五月の場合」より

Android のコンポーネント(Activity)の単体テスト3 - 別アクティビティからの結果を取得する

タイトル長いな……。簡潔に言うと onActivityResult のテストをする方法です。

前回の続きになります。

pickles-ochazuke.hatenablog.com

概要

前回と同じ Espresso.Intents を使います。Espresso-Intents については、前回話しているので飛ばします。

onActivityResult は、requestCode, resultCode, インテントデータを受け取ります。この内、resultCode と インテントデータの2つのデータは、別のアクティビティが setResult() で設定しています。この別のアクティビティが設定する部分をスタブ化します(スタブ化は、簡単に言えば、機能を真似するようにする。ということです)。

スタブ化するためには、Intents.intending() とその戻り値である OngoingStubbing のメソッド respondWith() を使います。

developer.android.com

developer.android.com

developer.android.com

次のように使います。

intending(hasComponent(OtherActivity::class.java.name)).respondWith(result)

使い方としては、intending() の引数にスタブ化したい対象が一致する条件を渡し、そのスタブ化の対象が呼ばれたときに渡したい結果を respondWith() に渡します。

上記だと OtherActivity が呼ばれた(OtherActivity が作成された)ときに、そのアクティビティが終了されたものとし、result を onActivityResult() の各引数(resultCode, intent)に渡されます。

上手く説明出来ませんが、実際に使用すると感覚が掴めると思います。

実践では、前回のプロジェクトを使うので、なければこちらの ActivityUnitTestExample_2 ブランチを使うと同じ状態に出来ます。

github.com

実践

今回追加する処理は、前回のプロジェクトを使い、OtherActivity を起動したら挨拶の情報を持ったインテントが返ってくるので、それをテキストに反映させるという処理にします。テストで確認するだけなので OtherActivity 側は何も変更しません。ですので、実際のアプリで OtherActivity は何も返してきません。

まずは、別アクティビティから結果を受け取れるようにします。MainActivity.kt を以下のように変更します。

import android.widget.TextView
...
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        if (resultCode != RESULT_OK) { return }
        if (requestCode != 1) { return }

        findViewById<TextView>(R.id.helloWorld).text = data?.getStringExtra("greeting")
    }

ついでに、startActivity() から startActivityForResult() に変更します。

startActivityForResult(intent, 1)

次にテストを追加します。

import android.app.Activity
import android.app.Instrumentation
import android.content.Intent
import androidx.test.espresso.intent.Intents.intending

    @Test
    fun OtherActivityから受け取った結果がテキストに反映されるべき() {
        val intent = Intent().apply {
            this.putExtra("greeting", "Hi World!")
        }

        val result = Instrumentation.ActivityResult(Activity.RESULT_OK, intent)
        intending(hasComponent(OtherActivity::class.java.name)).respondWith(result)

        onView(withId(R.id.button)).perform(click())
        onView(withId(R.id.helloWorld)).check(matches(withText("Hi World!")))
    }

intending() 手前までは、ActivityResult を作成しています。intending() の引数は、スタブ化したい対象の条件を指定しています。そして、respondWith()ActivityResult を渡しています。

https://developer.android.com/reference/android/app/Instrumentation.ActivityResult

スタブ化が完了したら、Espresso で UI を操作し、ビューが期待通りになっているか検証します。デバッグ実行を行い、onStartActivity()onView(withId(R.id.button)).perform(click())ブレークポイントを設定するとが perform() 実行されたあとに onStartActivity() で止まることが確認できます。

以上です。

ここまで行った状態が途中に上げた Github にあるリポジトリActivityUnitTestExample_3 ブランチです。

雑記

GW で調べていたことを放出しました。本当は Todo アプリを作って Android の理解を深めようと思ったのですが、その Todo アプリをテストしようとしたらいろいろエラーを踏んで3,4日潰れました……。まだテストのことは理解が足りていないので間違ったことを書いているかもしれませんが、誰かの助けになれば幸いです(間違っていれば指摘しただけると助かります)。

今年のGWは、技術書読み進めたり、Android のテスト調べたり、これ書いたりと割と充実していました(それでもやり残したことはありますが)。テストのエラー解決できないとき諦めようと思いましたが時間あけて考え直すと解決したので、やっぱり諦めずにいるのは大事だなと思いました(そして適度な休息!)。

あと恋する小惑星を見て地学に興味湧いたので NHK の地学基礎って動画見てるんですがかなり面白くてノートにまとめたりしています。

koiastv.com

www.nhk.or.jp

ちなみに私はジャイアント・インパクト説派です(他の説あんまり知りませんが)。