LoginSignup
1
0
この記事誰得? 私しか得しないニッチな技術で記事投稿!

AndroidのJUnitテストは超ムズい(3)

Last updated at Posted at 2023-06-15

AndroidのJUnitテストは超ムズい、EspressoでInstrument test

AndroidのJUnitテストは超ムズい
AndroidのJUnitテストは超ムズい(2)
の続編です。

Robolectricでだいたいいい感じでJUnitテストができたので、RobolectricではできなかったテストをEspressoを使ってInstrument testで書いてみます。

そもそもテストが起動しない

初っ端からエラーでテストが起動しません。(´・ω・`)

java.lang.NoClassDefFoundError: io.mockk.impl.JvmMockKGateway
at io.mockk.junit4.MockKRule.finished(MockKRule.kt:64)
at org.junit.rules.TestWatcher.finishedQuietly(TestWatcher.java:122)
at org.junit.rules.TestWatcher.access$400(TestWatcher.java:52)
at org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:70)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)

(・・・中略・・・)

... 28 more
Caused by: io.mockk.proxy.MockKAgentException: MockK could not self-attach a jvmti agent to the current VM. This feature is required for inline mocking.
This error occured due to an I/O error during the creation of this agent: java.io.IOException: Unable to dlopen libmockkjvmtiagent.so: dlopen failed: library "libmockkjvmtiagent.so" not found

どうやらmockkが出している例外のようです。

libmockkjvmtiagent.so: dlopen failed: library "libmockkjvmtiagent.so" not found

って言ってるけど、libmockkjvmtiagent.soってlinuxのshared libraryじや?今、Windows版で動かしているのに・・・
ネットで色々検索すると、gitHubのissueにありました。

build.gradle
android {
    ・・・
    testOptions {
        ・・・
        packagingOptions {
            jniLibs {
                useLegacyPackaging = true // espresso, mockk
            }
        }
    }
}

を追加するとこの事象は解消しました。何故かはわかりません。プロジェクトによって出る/出ないがあります。

これが、解消したらまた次。(´・ω・`)

java.lang.IncompatibleClassChangeError: Superclass kotlinx.coroutines.flow.StateFlow of kotlinx.coroutines.flow.StateFlow_1_Proxy is an interface (declaration of 'kotlinx.coroutines.flow.StateFlow_1_Proxy' appears in /data/user/0/jp.co.suzuken.materialrequest/app_dxmaker_cache/Generated_-767958641.jar)
at java.lang.VMClassLoader.findLoadedClass(Native Method)
at java.lang.ClassLoader.findLoadedClass(ClassLoader.java:738)
at java.lang.ClassLoader.loadClass(ClassLoader.java:363)
at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
at com.android.dx.stock.ProxyBuilder.loadClass(ProxyBuilder.java:358)
at com.android.dx.stock.ProxyBuilder.buildProxyClass(ProxyBuilder.java:340)
at io.mockk.proxy.android.transformation.AndroidSubclassInstrumentation.subclass(AndroidSubclassInstrumentation.kt:37)
at io.mockk.proxy.common.ProxyMaker.subclass(ProxyMaker.kt:144)
at io.mockk.proxy.common.ProxyMaker.proxy(ProxyMaker.kt:53)
at io.mockk.impl.instantiation.JvmMockFactory.newProxy(JvmMockFactory.kt:34)
at io.mockk.impl.instantiation.AbstractMockFactory.newProxy$default(AbstractMockFactory.kt:24)
at io.mockk.impl.instantiation.AbstractMockFactory.temporaryMock(AbstractMockFactory.kt:127)
at io.mockk.impl.recording.states.RecordingState$call$temporaryMock$1.invoke(RecordingState.kt:69)
at io.mockk.impl.instantiation.JvmAnyValueGenerator$anyValue$2.invoke(JvmAnyValueGenerator.kt:35)
at io.mockk.impl.instantiation.AnyValueGenerator.anyValue(AnyValueGenerator.kt:34)
at io.mockk.impl.instantiation.JvmAnyValueGenerator.anyValue(JvmAnyValueGenerator.kt:31)
at io.mockk.impl.recording.states.RecordingState.call(RecordingState.kt:75)
at io.mockk.impl.recording.CommonCallRecorder.call(CommonCallRecorder.kt:53)
at io.mockk.impl.stub.MockKStub.handleInvocation(MockKStub.kt:270)
at io.mockk.impl.instantiation.JvmMockFactoryHelper$mockHandler$1.invocation(JvmMockFactoryHelper.kt:24)

うーむ。これもmockk絡み。mockkでViewModelでStateFlowをmock化しているところで出ています。mockkとInstrument testの相性が悪いのかな?と思ったのですが、mockkはちゃんとInstrument testをサポートしています。

(Android P未満とP以上でサポートしている機能が違うのがよくわからん・・・)

これも、ネットを色々検索した結果(GPT先生はまともな答えを返さなかった)gitHubのissueに解決方法がありました。
mockk-1.13.4、1.13.5で出る特有の問題のようです。1.13.3に下げることで解消しました。

どうも、mockkはInstrument testをサポートしいるものの、バージョンには敏感なようです(他のライブラリのバージョンとも関連しているのか?)。これら事象は将来のバージョンで解消されるかもしれません。

build.gradle
dependencies {
    ・・・
    androidTestImplementation 'io.mockk:mockk-android:1.13.3'
    androidTestImplementation 'io.mockk:mockk-agent:1.13.3'
}

具体例、RecyclerViewのswipe

これでやっと、Instrument test開始できるようになりました。
やりたかったテストがRecyclerViewの左右のswipeでItemTouchHelperが呼ばれるのをEspressoでテストしたかったのですが、どうすればいいのか?

val ith = ItemTouchHelper(
    object : ItemTouchHelper.SimpleCallback(
        ItemTouchHelper.UP or ItemTouchHelper.DOWN,
        ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
    ){
        override fun onMove(
            recyclerView: RecyclerView,
            viewHolder: RecyclerView.ViewHolder,
            target: RecyclerView.ViewHolder
        ): Boolean {
            return false // 移動はさせない
        }
        override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
            // ★ ここをテストしたい
        }
    })
ith.attachToRecyclerView(recyclerView)

RecyclerViewのswipeをテストするためにはespresso-contribを依存関係に追加します。

build.gradle
dependencies {
    ・・・
    androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.5.1'
}

テストコードは、以下の様になります。

val scenario = launchFragmentInContainer <MyFragment>()
scenario.onFragment { fragment ->
}
// assert
onView(withId(R.id.myView)).check(
    ViewAssertions.matches(ViewMatchers.hasChildCount(2))
)
// 左にswipe
onView(withId(R.id.materialView)).perform(
    RecyclerViewActions.actionOnItemAtPosition<MyAdapter.ViewHolder>(0, swipeLeft())
)
// assert
onView(withId(R.id.materialView)).check(
    ViewAssertions.matches(ViewMatchers.hasChildCount(1)) // ★ FIXME 必ずしも1にならない
)

最初にRecyclerViewに2件あることを確認して、左にswipe、1件に減っているはずですが、★で、必ずしも1にはなりません。
それは、Espressoは画面のViewを操作(左にswipe)しているだけで、RecuclerViewの件数はRecyclerView.Adapterが制御しています。
通常は、左にswipeしたことによりその裏の処理、ItemTouchHelper.SimpleCallback#onSwiped でRecyclerView.Adapterの件数から1件削除する処理があるはずです。ここをmock化してしまったら削除されないので、件数は変わりません。

mockkの書き方によってはそこは0番目を削除したように振る舞うことができますが、そうでない場合は注意が必要です。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0