英文:
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
val restaiurantsState: LiveData<ResultState<List
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
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
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 https://stackoverflow.com/questions/58303961/kotlin-coroutine-unit-test-fails-with-module-with-the-main-dispatcher-had-faile
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."
英文:
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 https://stackoverflow.com/questions/58303961/kotlin-coroutine-unit-test-fails-with-module-with-the-main-dispatcher-had-faile
> 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.
答案1
得分: 1
你应该尝试在设置函数中初始化你的视图模型,例如:
private lateinit var viewmodel: RestaurantsViewModel
@Before
fun setup() {
viewmodel = RestaurantsViewModel(repository)
}
在声明位置初始化视图模型会在测试规则执行之前发生,因此视图模型会使用主调度程序。
英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论