android ViewModel StateFlow单元测试-等待60000 ms后,测试协程未完成

8ljdwjyq  于 2023-06-20  发布在  Android
关注(0)|答案(1)|浏览(201)

我正在Android项目上工作,并进行API调用以从服务器获取数据。我使用Retrofit进行网络连接,使用StateFlow存储数据并将数据传递到UI层。
它运行得非常好,行为明智。
我为ViewModel写了一个单元测试,成功,失败,但是我得到了一个协程超时错误,看起来像是有什么东西从协程中泄漏出来。我也不是很确定。任何帮助将不胜感激。
我还检查了StackOverflow的一些答案,但没有帮助

我也愿意为视图模型重写测试用例,包括加载,成功和失败场景。

错误

  1. After waiting for 60000 ms, the test coroutine is not completing
  2. kotlinx.coroutines.test.UncompletedCoroutinesError: After waiting for 60000 ms, the test coroutine is not completing
  3. at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt$runTestCoroutine$3$3.invokeSuspend(TestBuilders.kt:342)
  4. (Coroutine boundary)
  5. at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt.runTestCoroutine(TestBuilders.kt:326)
  6. at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt$runTest$1$1.invokeSuspend(TestBuilders.kt:167)
  7. at kotlinx.coroutines.test.TestBuildersJvmKt$createTestResult$1.invokeSuspend(TestBuildersJvm.kt:13)
  8. Caused by: kotlinx.coroutines.test.UncompletedCoroutinesError: After waiting for 60000 ms, the test coroutine is not completing
  9. at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt$runTestCoroutine$3$3.invokeSuspend(TestBuilders.kt:342)
  10. at app//kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
  11. at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)

视图模型

  1. private val _uiCaseListState = MutableStateFlow<Resource<List<Case>>?>(null)
  2. val uiCaseListState: StateFlow<Resource<List<Case>>?> get() = _uiCaseListState
  3. fun fetchCaseList() {
  4. viewModelScope.launch(Dispatchers.IO) {
  5. inboxRepository.fetchCaseList().collect { response ->
  6. when {
  7. response.isSuccessful -> {
  8. response.body()?.let { caseList ->
  9. val filterCaseList = caseList.sortedByDescending { case ->
  10. case.createdDate.time
  11. }
  12. _uiCaseListState.emit(Resource.Success(filterCaseList))
  13. }
  14. }
  15. !response.isSuccessful -> {
  16. _uiCaseListState.emit(Resource.Failure(ErrorCode.GENERAL_ERROR))
  17. }
  18. else -> {
  19. _uiCaseListState.emit(Resource.Failure(ErrorCode.GENERAL_ERROR))
  20. }
  21. }
  22. }
  23. }
  24. }

单元测试

  1. @Test
  2. fun fetchCaseList_Success_CaseList() = runTest {
  3. // Arrange
  4. val repo = InboxRepository(object : InboxApi {
  5. override suspend fun fetchCaseList(): Response<List<Case>?> {
  6. return Response.success(testCaseList)
  7. }
  8. })
  9. val vm = InboxViewModel(repo)
  10. // Act
  11. vm.fetchCaseList()
  12. // Assert
  13. assertEquals(Resource.Success(testCaseList).data.size, vm.uiCaseListState.drop(1).first()!!.data!!.size)
  14. }
  15. /**
  16. * "kotlinx.coroutines.test.UncompletedCoroutinesError:After waiting for 60000 ms, the test coroutine is not completing"
  17. */
  18. @Ignore
  19. @Test
  20. fun fetchCaseList_Failure_GENERAL_ERROR() = runTest {
  21. // Arrange
  22. val error: Response<List<Case>?> = Response.error(
  23. 400,
  24. "{\"key\":[\"someError\"]}"
  25. .toResponseBody("application/json".toMediaTypeOrNull())
  26. )
  27. val repo = InboxRepository(object : InboxApi {
  28. override suspend fun fetchCaseList(): Response<List<Case>?> {
  29. return error
  30. }
  31. })
  32. val vm = InboxViewModel(repo)
  33. // Act
  34. vm.fetchCaseList()
  35. // Assert
  36. assertEquals(Resource.Failure<Error>(ErrorCode.GENERAL_ERROR), vm.uiCaseListState.drop(1).first())
  37. }
gxwragnw

gxwragnw1#

代码的问题是在测试时没有办法替换dispatcher。如果任务运行在您无法控制的后台线程上,则很难在正确的时间执行Assert或等待任务完成。
解决方案是通过构造函数在viewModel中注入dispatcher:

  1. InboxViewModel(val repo: InboxRepository,val ioDispatcher: CoroutineDispatcher = Dispatchers.IO)
  2. viewModelScope.launch(ioDispatcher) {
  3. inboxRepository.fetchCaseList().collect { response ->
  4. when {
  5. response.isSuccessful -> {
  6. response.body()?.let { caseList ->
  7. val filterCaseList = caseList.sortedByDescending { case ->
  8. case.createdDate.time
  9. }
  10. _uiCaseListState.emit(Resource.Success(filterCaseList))
  11. }
  12. }
  13. !response.isSuccessful -> {
  14. _uiCaseListState.emit(Resource.Failure(ErrorCode.GENERAL_ERROR))
  15. }
  16. else -> {
  17. _uiCaseListState.emit(Resource.Failure(ErrorCode.GENERAL_ERROR))
  18. }
  19. }
  20. }
  21. }
  22. }

在测试中,您可以注入TestDispatcher来替换Dispatchers.IO

  1. @Test
  2. fun fetchCaseList_Success_CaseList() = runTest {
  3. // Arrange
  4. val repo = InboxRepository(object : InboxApi {
  5. override suspend fun fetchCaseList(): Response<List<Case>?> {
  6. return Response.success(testCaseList)
  7. }
  8. })
  9. val vm = InboxViewModel(repo, Dispatchers.Unconfined)
  10. // Act
  11. vm.fetchCaseList()
  12. // Assert
  13. assertEquals(Resource.Success(testCaseList).data.size, vm.uiCaseListState.drop(1).first()!!.data!!.size)
  14. }
展开查看全部

相关问题