AndroidプロジェクトでのUnitTest環境をセットアップする
(最近の)Androidのプロジェクトにおいて、新しくUnitTestを書くところまでのセットアップのトラブルシューティングを残しておく。
環境
Android Studio: v4.0 使用しているTest Libraryとversionは以下の通り
- "org.mockito:mockito-core:2.23.0"
- "com.nhaarman.mockitokotlin2:mockito-kotlin:2.0.0"
- 'androidx.test.ext:junit:1.1.1'
- 'androidx.test.espresso:espresso-core:3.2.0'
- "org.mockito:mockito-android:2.23.0"
- "android.arch.core:core-testing:2.1.0-alpha02"
- "com.jraska.livedata:testing-ktx:1.1.2"
- "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.4"
- "org.robolectric:robolectric:4.3"
Cannot invoke observeForever on a backgground thread
以下のコードのようにViewModelのUnitTestでLiveDataのobserveされる値をテストする時に発生した。
@Test fun `getCountryList_success`() { val list = listOf("USA") repository = mock { onBlocking { fetchCountryList() }.thenReturn(list) } viewModel = MainViewModel(repository, Dispatchers.Main) val testObserver = viewModel.countryList.test() runBlocking { viewModel.getCountryList() val history = testObserver.valueHistory() Assert.assertEquals(history[0], list) verify(repository).fetchCountryList() } }
解決策
DroidKaigi/conference-app-2020 の ViewModelTestRule を参考にTestRuleを定義/設定する。
@Rule @JvmField val viewModelTestRule = ViewModelTestRule()
MockitoException
Mockito cannot mock/spy because : final class
ViewModelのテストでRepositoryをMockしようとした時に発生。 MockしようとしていたRepositoryはKotlinで書かれたclassであったため、final class扱いとなっていたため。
解決策
- Repositoryにopenをつけて継承可能にする。 Mockは無事できるようになるが、UnitTestのためにプロダクトコードのfinal classの状態を変更するのはあまり良い解決策とは思えない。
open class CountryRepository{ }
- Interfaceに切ってTestではinterfaceをmockする
interface CountryRepository{ } class CountryRepositoryImpl() : CountryRepository{ }
ViewModelの引数ではInterfaceの型で受けるようにし、UnitTestではinterfaceの型をmockする。
@Mock lateinit var repository: CountryRepository
Koinで設定したDIには as
でどのinterfaceとして扱われるかを明示しておくとプロダクションコードでも問題なく動く。
val repositoryModule = module { single { CountryRepositoryImpl(get(), get(), get()) as CountryRepository } }
- その他
mockito-extensions
というresource directoryを切って設定する方法もあるみたいだが、自分は解決されなかった。おとなしくinterfaceに切るのがTestableな設計になるのだと思う。
KotlinクラスをMockito2でMockするには - Qiita
IllegalArgumentException: API level 29 is not available
Robolectric起因のErrorで何回も遭遇する…
解決策
src/test/recources
の配下に robolectric.properties
というfileを作成し以下を記入
sdk=28
@Config
をTest classごとにつけてもOKだがすべてのTest classに個別に追加しなくてはいけないので共通の設定は properties fileに記しておく。
A KoinContext is already started
Koinに限らずApplicationクラスで定義されているinit処理系でerrorになったら同じ問題。 AndroidTestは特にTestが走るたびにApplicationも起動してしまうのでTest用のApplicationに差し替える。
解決策
Robolectric 3.0でApplicationクラスやConstantsクラスをrobolectric.propertiesに書き出す時の注意点 - Tatuas Blog
test directoryにTest用のTestApplication.kt
を作成する
robolectric.properies
に
application=com.chiiia12.sampleapp.TestApplication
の定義を追加すると解決する。
などなど、普段1からセットアップする機会もなかったりするのでテストコードを書くまでに時間がかかると萎えるので今後同じことで詰まった時に参照したし。