HukeLau_DABA
HukeLau_DABA

Reputation: 2528

kotlin coroutine unit test no longer works

I'm using an old technique that no longer works on my viewmodel tests now for some reason.

In order the coroutine created via viewmodelScope.launch we need to reset the main dispatcher with a test dispatcher. I do this via MainDispatcherRule as described in https://developer.android.com/kotlin/coroutines/test#setting-main-dispatcher

@ExperimentalCoroutinesApi
class MainDispatcherRule(
    val testDispatcher: TestDispatcher = UnconfinedTestDispatcher(),
) : TestWatcher() {

    override fun starting(description: Description) {
        Dispatchers.setMain(testDispatcher)
    }

    override fun finished(description: Description) {
        Dispatchers.resetMain()
    }
}

My dependencies

dependencies {

implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.8.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.core:core-ktx:1.9.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'

implementation "com.squareup.retrofit2:converter-gson:2.9.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
implementation "androidx.activity:activity-ktx:1.6.0"
implementation "androidx.fragment:fragment-ktx:1.5.3"

// testing
// for runTest, CoroutineDispatcher
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.0"
// for InstantTaskExecutorRule
testImplementation "androidx.arch.core:core-testing:2.1.0"
testImplementation "io.mockk:mockk:1.13.2"

implementation "com.google.dagger:hilt-android:2.44"
kapt "com.google.dagger:hilt-compiler:2.44"

}

RestaurantsViewModel

@HiltViewModel
class RestaurantsViewModel @Inject constructor(private val repo: RestaurantsRepository): ViewModel() {

    private val _restaurantsState = MutableLiveData<ResultState<List<Restaurant>>>()
    val restaiurantsState: LiveData<ResultState<List<Restaurant>>> = _restaurantsState


    init {
        initialize()
    }

    fun initialize() {
        viewModelScope.launch {
            _restaurantsState.postValue(ResultState.Loading)
            try {
                _restaurantsState.postValue(
                    ResultState.Success(repo.getRestaurants())
                )
            } catch (ex: Exception) {
                _restaurantsState.postValue(
                    ResultState.Failure("Failed to fetch restaurants", ex)
                )
            }
        }
    }
}

its associated test in RestaurantsViewModelTest

@ExperimentalCoroutinesApi
class RestaurantsViewModelTest {

    private var repository = mockk<RestaurantsRepository>(relaxed = true)
    private var viewmodel = RestaurantsViewModel(repository)

    @get:Rule
    val mainDispatcherRule = MainDispatcherRule()

    @get:Rule
    val instantTaskExecutorRule = InstantTaskExecutorRule()

    @Test
    fun `restaurantsState LiveData is updated with the result from repository`() = runTest {
        val restaurants = listOf(mockk<Restaurant>(relaxed = true))
        val success = ResultState.Success(restaurants)
        coEvery { repository.getRestaurants() } returns restaurants

        viewmodel.initialize()

        Assert.assertEquals(success, viewmodel.restaurantsState.value)
    }

is met with the error described in Kotlin coroutine unit test fails with "Module with the Main dispatcher had failed to initialize"

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

My unit test compiles as is but fails to pass.

Upvotes: 0

Views: 1976

Answers (1)

Joe Malebe
Joe Malebe

Reputation: 714

You should try initialise your view model in a setup function e.g

private lateinit var viewmodel:RestaurantsViewModel

@Before
fun setup() {
  viewmodel = RestaurantsViewModel(repository)
}

Initialising your view model at the declaration site happens before the test rule executes, therefore the view model is using the main dispatcher.

Upvotes: 1

Related Questions