たかぎとねこの忘備録

プログラミングに関する忘備録を自分用に残しときます。マサカリ怖い。

ViewModelを初期化しようとして`Module with the Main dispatcher had failed to initialize`というエラーが出た話

テスト時にViewModelを初期化する処理を次のように書いた。

class LabelListViewModelTest {
    private val testScheduler = TestCoroutineScheduler()
    private val dispatcher = StandardTestDispatcher(testScheduler)
    private val repository = mockk<LabelRepository>(relaxed = true)
    private val sut = LabelListViewModel(repository)

    @Before
    fun setup() {
        Dispatchers.setMain(dispatcher)
    }

そしたら、次のようなエラーが発生した。

Exception in thread "Test worker" java.lang.IllegalStateException: Module with the Main dispatcher had failed to initialize. For tests Dispatchers.setMain from kotlinx-coroutines-test module can be used

ViewModelの中身を確認してみる。

...
@HiltViewModel
class LabelListViewModel @Inject constructor(
    private val labelRepository: LabelRepository
) : ViewModel() {
    private val viewModelState = MutableStateFlow(
        LabelListViewModelState(
            isLoading = false,
        )
    )
    val uiState = viewModelState
        .map { it.toUiState() }
        .stateIn(
            viewModelScope,
            SharingStarted.Eagerly,
            viewModelState.value.toUiState()
        )

おそらく、setup()Dispatchers.setMain()を呼び出す前に、.stateIn(viewModelScope, SharingStarted.Eagerly, viewModelState.value.toUiState())が呼び出されてしまっているためModule with the Main dispatcher had failed to initializeというエラーが発生したのだと思う。

なのでlateinitを使ってsetup()の中でsutを初期化するようにしたらエラーが消えた。

...
@HiltViewModel
class LabelListViewModel @Inject constructor(
    private val labelRepository: LabelRepository
) : ViewModel() {
    private val viewModelState = MutableStateFlow(
        LabelListViewModelState(
            isLoading = false,
        )
    )
    val uiState = viewModelState
        .map { it.toUiState() }
        .stateIn(
            viewModelScope,
            SharingStarted.Eagerly,
            viewModelState.value.toUiState()
        )