環境
- 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
型として保持しているためです.