Kotlin协程单元测试不再起作用。

huangapple go评论63阅读模式
英文:

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>> = _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(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(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."

英文:

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 &#39;androidx.appcompat:appcompat:1.6.1&#39;
implementation &#39;com.google.android.material:material:1.8.0&#39;
implementation &#39;androidx.constraintlayout:constraintlayout:2.1.4&#39;
implementation &#39;androidx.core:core-ktx:1.9.0&#39;
testImplementation &#39;junit:junit:4.13.2&#39;
androidTestImplementation &#39;androidx.test.ext:junit:1.1.5&#39;
androidTestImplementation &#39;androidx.test.espresso:espresso-core:3.5.1&#39;

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

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

implementation &quot;com.google.dagger:hilt-android:2.44&quot;
kapt &quot;com.google.dagger:hilt-compiler:2.44&quot;

}

RestaurantsViewModel

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

    private val _restaurantsState = MutableLiveData&lt;ResultState&lt;List&lt;Restaurant&gt;&gt;&gt;()
    val restaiurantsState: LiveData&lt;ResultState&lt;List&lt;Restaurant&gt;&gt;&gt; = _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(&quot;Failed to fetch restaurants&quot;, ex)
                )
            }
        }
    }
}

its associated test in RestaurantsViewModelTest

@ExperimentalCoroutinesApi
class RestaurantsViewModelTest {

    private var repository = mockk&lt;RestaurantsRepository&gt;(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&lt;Restaurant&gt;(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.

huangapple
  • 本文由 发表于 2023年4月20日 00:15:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/76056752.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定