お茶漬けびより

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

Android のコンポーネント(Activity)の単体テスト2 - 別アクティビティの起動

一応、前回の続きです。

pickles-ochazuke.hatenablog.com

概要

今回は、テスト対象のアクティビティから別のアクティビティを起動するテストを作成します。

別のアクティビティが起動したかをテストするのですが、単体テストなので別のアクティビティに強く依存したくはありません。例えば、別のアクティビティを起動して、そのビューが期待通りになっているかどうかまでは確認する必要はないと思います。これは、そのアクティビティ(別のアクティビティ)側の単体テストの役割だからです。

アクティビティを起動する場合は、インテントを作成し、startActivity() メソッドにインテントを渡すことで Android 側が処理してくれます。ですので、startActivity() メソッドに期待するインテントが渡されていれば起動できていることにしても良さそうです。

単体テストでは、実機を使うわけではないので、Android のシステムのようにインテントを管理してくれる代わりが必要になります。これは、Espresso では、Intents クラスがしてくれます。Espresso のドキュメントでは用語が紛らわしいためか、Espresso-Intents と表現しています。

developer.android.com

この Intents クラスを初期化していると、startActivity() メソッドに渡されたインテントが Intents クラスに記録されます。その後、Intents のインテント検証用のメソッド(intended() メソッド)を使うことで期待する値になっているか検証できます。

例えば、次のように使います。

Intents.intended(hasComponent(OtherActivity::class.java.name))

intended() メソッドに渡した条件(上記だと OtherActivity クラスの名前を持ったインテントがあるかどうか)を元に Intents が記録しているインテントを検証します。検証した結果、一致したのが一つのときテストが成功します。複数のインテントが一致した場合や、一つも一致しない場合は例外が投げられテストが失敗します。

developer.android.com

実践

プロジェクトの準備

ここからは、実際にテストをしてみます。プロジェクトは、前回のプロジェクトをそのまま使います。

前回の最後の状態のプロジェクトは、Github に上げています。ブランチは、ActivityUnitTestExample_1 です。

github.com

別のアクティビティ起動の実装

まずは、別のアクティビティを起動する処理を実装します。ここでは、ボタンを押したらアクティビティが起動するようにしたいと思います。

別のアクティビティを起動するためには、そのアクティビティが必要になので、OtherActivity という名前で作成しておきます。中身は一切編集しないので、テンプレートは何でもいいですが、ここでは EmptyActivity にしました。

次にボタンを追加します。activity_main.xml にボタンを追加し、id を button とします。

レイアウトにボタンを追加したら MainActivity.kt にボタンの処理を実装します。

import android.widget.Button
import android.content.Intent

...
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val button = findViewById<Button>(R.id.button)
        button.setOnClickListener {
            val intent = Intent(this, OtherActivity::class.java)
            startActivityForResult(intent, 1)
        }
    }
...

実装に不安があれば、ここで一度エミュレータや実機で確認しましょう。

テストの追加

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

import androidx.test.espresso.action.ViewActions.click

import androidx.test.espresso.intent.Intents.intended
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent

...
    @Test
    fun ボタンを押すとOtherActivityが起動するべき() {
        onView(withId(R.id.button)).perform(click())

        intended(hasComponent(OtherActivity::class.java.name))
    }

Espresso-Intents は拡張機能のため、app/build.gradle に以下を追加します。

dependencies {
  ...
  testImplementation 'androidx.test.espresso:espresso-intents:3.2.0'
  ...
}

Gradle の同期を行い、テストを実行すると java.lang.NullPointerException が発生すると思います。これは、Intents の初期化がされていないのに intended() メソッドを呼んだためです。Intents.init() メソッドを呼ぶと初期化されますが、テストの終了時に Intents.release() を呼ぶ必要があります。

前回、Activity を管理してくれる ActivityScenarioRule というクラスを使いました。これと似たようなものが Intents 用に存在します。IntentsTestRule です。

activityScenarioRule() が書かれた箇所を次のように置き換えます。

import androidx.test.espresso.intent.rule.IntentsTestRule

    @get:Rule
    val rule = IntentsTestRule(MainActivity::class.java)

activityScenarioRule を残していると、アクティビティが二回作成され、テストが上手くいきません。原因は分かっていませんが、おそらく onView() で処理しているアクティビティの対象が activityScenarioRule の方を見ており、その場合、インテントが記録されないためです。

上記を追加したあと、テストを実行すると成功するはずです。

以上です。

ここまでの状態を Github のほうに上げました。ブランチを ActivityUnitTestExample_2 に変更するとその状態になります。

github.com

続きを書きました。

pickles-ochazuke.hatenablog.com

雑記

Kotlin のコードが上手く色づけされないのって何でですかね(対応しているらしいですが)。書き方が間違っているのか、何か設定しないといけないのか。全体的に見た目が気になりだしたのてこれを気にいろいろ弄ってみてもいいのかもしれない。