環境
- Kotlin: 1.3.21
- lifecycle-extensions: 2.1.0-alpha03
- mockk: 1.9
テスト時にはDaggerを経由してViewModelFactoryおよびViewModelが生成され, テスト時もDaggerを用いてViewModelを生成しています.
詳しい方法についてはこちらのQiita記事を参照してください.
問題
ViewModel を使った Activity/Fragment のテストをするとき, 次のような, モデルクラスのListを返すようなLiveDataを持ったViewModelを定義して使っているものとします.
このとき, ArticleViewModel$articles を Activity/Fragment から上記のような形で呼び出した時に ClassCastException が発生してしまいます.
ArticleViewModel.kt
open class ArticleViewModel() : ViewModel() {
private val _articles by lazy { MutableLiveData<List<Article>>() }
val articles: LiveData<List<Article>> get() = _articles
// ...
}`ArticlesFragment.kt
val articles = viewModel.articles.valueエラー
java.lang.ClassCastException: java.lang.Object cannot be cast to java.util.Collection解決策
通常は次のような記述でViewModelを記述しますが, 今回は Activity/Fragment のテストをしたいのでViewModelをモック化します.
@Module
interface ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(ArticleViewModel::class)
fun articleViewModel(viewModel: ArticleViewModel): ViewModel
}モック化した ArticleViewModel を提供するには, 上記の記述の代わりに次のような記述をします.
@Module
class MockViewModelModule {
@Provides
@IntoMap
@ViewModelKey(ArticleViewModel::class)
fun articleViewModel(): ViewModel = mockk<ArticleViewModel>(relaxed = true) {
every { articles } returns MutableLiveData<List<Article>>(listOf(dummyArticle()))
}
}every { articles } ... の部分が重要で, 単純に relaxed = true だと先に記述したようなエラーが発生してしまいます.
そもそも, List<Article> という型を指定しているにも関わらず Object からキャストしようとしているのは, LiveData の内部ではどんな型であろうと Object 型として保持しているためです.