環境

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