環境
-
androidx.core.core
: v1.9.0 - kotlin : v1.8.10
-
compileSDK
: 33 -
org.robolectric:robolectric
: v4.10.3
起きたこと
class ExampleUnitTest {
@Test
fun testA() {
val pair = Pair(1, 2)
assert(pair.first == 1)
assert(pair.second == 2)
}
}
こんなテストを、実行するとなぜか失敗。
原因
→原因は、ここで呼んでいたPair
がandroid.util.Pair
だったから。
import android.util.Pair
class ExampleUnitTest {
@Test
fun testA() {
val pair = Pair(1, 2)
assert(pair.first == 1)
assert(pair.second == 2)
}
}
Pairについて
Androidアプリを作成する際に使えるPair
にはandroid.util.Pair
とandroidx.core.util.Pair
とkotlin.Pair
があります。
全て(Kotlinから使う場合)コンストラクタPair<F,S>(F first, S second)
を使用してインスタンスを作れますが、android.util.Pair
だけは、そのコンストラクタを使用してインスタンスを作ってもUnitテストではfirst
やsecond
は引数に渡したものにならずnullになるみたいです。 注意。
成功するテスト(各Pair
のfirst
second
の値)は以下。
class ExampleUnitTest {
@Test
fun android_util_pair() {
val pair = android.util.Pair(1, 2)
assert(pair.first == null)
assert(pair.second == null)
}
@Test
fun androidx_core_util_pair() {
val pair = androidx.core.util.Pair(1, 2)
assert(pair.first == 1)
assert(pair.second == 2)
}
@Test
fun kotlin_pair() {
val pair = kotlin.Pair(1, 2)
assert(pair.first == 1)
assert(pair.second == 2)
}
}
なぜ?
「Unitテストでは」と強調しているところからも察することができるかもしれないですが、android.util.Pair<F,S>(F first, S second)
を使ってインスタンスを作成してもfirst
、second
ともにnullになるのはUnitテストのみです。
実機で動かした場合は、ちゃんとfirst
もsecond
も渡した値になります。
@Composable
fun Test(name: String, modifier: Modifier = Modifier) {
val pair = android.util.Pair(1, 2)
Log.d("test", "${pair.first} ${pair.second}")
}
1 2
というのもandroid.util.Pair
はandroid.util.Log
などと同様に、実際の実装はAndroid端末の方にあるメソッドだからです。
ちなみに①
@RunWith(RobolectricTestRunner::class)
をつければandroid.util.Pair
のUnitテストでもOKです。(Context
などと同じくです。)
@RunWith(RobolectricTestRunner::class)
class ExampleUnitTest{
@Mock
val pair = android.util.Pair.create(1, 2)
@Test
fun android_util_pair() {
assert(pair.first == 1)
assert(pair.second == 2)
}
}
ちなみに②
android.util.Pair
の公式ドキュメント を見るとandroid.util.Pair<F, S> #create (F f,S s)
というstaticメソッドもPairインスタンスを作るメソッドとしてあります。
こちらのメソッドは、@RunWith(RobolectricTestRunner::class)
なしUnitテストだと**RuntimeException
が発生** / @RunWith(RobolectricTestRunner::class)
ありUnitテストだとちゃんと動きます。
class ExampleUnitTest{
val pair = android.util.Pair.create(1, 2)
@Test
fun android_util_pair() {
assert(pair.first == 1)
assert(pair.second == 2)
}
}
Method create in android.util.Pair not mocked. See https://developer.android.com/r/studio-ui/build/not-mocked for details.
java.lang.RuntimeException: Method create in android.util.Pair not mocked. See https://developer.android.com/r/studio-ui/build/not-mocked for details.
at android.util.Pair.create(Pair.java)
at com.example.myapplication.ExampleUnitTest.<init>(ExampleUnitTest.kt:18)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
(略)
@RunWith(RobolectricTestRunner::class)
class ExampleUnitTest{
@Mock
val pair = android.util.Pair.create(1, 2)
@Test
fun android_util_pair() {
assert(pair.first == 1)
assert(pair.second == 2)
}
}
@RunWith(RobolectricTestRunner::class)あり結果 -> テストpass
まとめ
プロダクトコードでは3つのPair全てで同じ動きになるものの、Unitテストを書くことを考えるとandroid.util.Pair
は使わないようにした方がよさそうです。
- mockのためのコードを書かなくていい・ビルド時間も削減
- そもそもmockに置き換えるということは正確性に若干欠ける
- テスト時に
android.util.Pair<F, S> #create (F f,S s)
の方は例外が発生するのでまだわかりやすいが、android.util.Pair<F,S>(F first, S second)
コンストラクタの方は「値が想定と異なっていた」という意味での失敗しか起きないのでテストに失敗したときに原因が瞬時に察せないかもしれない。- (まさに私はこれで原因の切り分けにとても時間を使ってしまいました。。。nullではなく、例外発生にしてほしかったなぁというのが正直な気持ち。)