获得Kotlin错误“等待60000毫秒后,Test Coroutine未完成”。

发布于 2025-02-05 03:36:32 字数 1204 浏览 2 评论 0原文

我是新手的测试,试图进行第二流量并断言它,当我一一运行该测试时,运行良好,但是当我运行整个测试时,一旦第一次测试运行正常,其余的测试就会给我超时错误。

错误 :

After waiting for 60000 ms, the test coroutine is not completing
kotlinx.coroutines.test.UncompletedCoroutinesError: After waiting for 60000 ms, the test coroutine is not completing
    at app//kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt$runTestCoroutine$3$3.invokeSuspend(TestBuilders.kt:304)
    (Coroutine boundary)
@OptIn(ExperimentalCoroutinesApi::class)
class HomeViewModelTest {

    private lateinit var viewModel: HomeViewModel
    private val testDispatcher = UnconfinedTestDispatcher()

    @Before
    fun setup() {
        viewModel = HomeViewModel(FakeOrderRepository())
        Dispatchers.setMain(testDispatcher)
    }

    @After
    fun tearDown() {
        Dispatchers.resetMain()
        testDispatcher.cancel()
    }

    @Test
    fun flowViewModelTesting1() = runTest {
        val result = viewModel.homeUiState.drop(1).first()
        assertThat(true).isTrue()
    }


    @Test
    fun flowViewModelTesting2() = runTest {
        val result = viewModel.homeUiState.drop(1).first()
        assertThat(true).isTrue()
    }
}

I'm new at testing, trying to take second flow value and assert it, When i run this test one by one runs fine but when i run whole test once first test runs fine and rest of test give me timeout error.

Error :

After waiting for 60000 ms, the test coroutine is not completing
kotlinx.coroutines.test.UncompletedCoroutinesError: After waiting for 60000 ms, the test coroutine is not completing
    at app//kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt$runTestCoroutine$3$3.invokeSuspend(TestBuilders.kt:304)
    (Coroutine boundary)
@OptIn(ExperimentalCoroutinesApi::class)
class HomeViewModelTest {

    private lateinit var viewModel: HomeViewModel
    private val testDispatcher = UnconfinedTestDispatcher()

    @Before
    fun setup() {
        viewModel = HomeViewModel(FakeOrderRepository())
        Dispatchers.setMain(testDispatcher)
    }

    @After
    fun tearDown() {
        Dispatchers.resetMain()
        testDispatcher.cancel()
    }

    @Test
    fun flowViewModelTesting1() = runTest {
        val result = viewModel.homeUiState.drop(1).first()
        assertThat(true).isTrue()
    }


    @Test
    fun flowViewModelTesting2() = runTest {
        val result = viewModel.homeUiState.drop(1).first()
        assertThat(true).isTrue()
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

哆啦不做梦 2025-02-12 03:36:32

我也有同样的问题。用 standardTestDispatcher() 将解决该问题。

I had the same issue. Replacing UnconfinedTestDispatcher() with StandardTestDispatcher() will solve the problem.

梦在深巷 2025-02-12 03:36:32
@RunWith(AndroidJUnit4::class)
class Test {

    private val dispatcher = TestCoroutineDispatcher()
    private val testScope = TestCoroutineScope(dispatcher)
    
    @Before
    fun setUp() {
        Dispatchers.setMain(dispatcher)
    }
    @After
    fun tearDown() {
        Dispatchers.resetMain()
        dispatcher.cleanupTestCoroutines()
        unmockkAll()
    }
    
    @OptIn(ExperimentalCoroutinesApi::class)
    @Test
    fun test() = runTest {
        testScope.launch {
                 // call your suspend method here
                Utils.testSuspendMethod()
            )
          //add here assertions for the method
        }

    }

}
@RunWith(AndroidJUnit4::class)
class Test {

    private val dispatcher = TestCoroutineDispatcher()
    private val testScope = TestCoroutineScope(dispatcher)
    
    @Before
    fun setUp() {
        Dispatchers.setMain(dispatcher)
    }
    @After
    fun tearDown() {
        Dispatchers.resetMain()
        dispatcher.cleanupTestCoroutines()
        unmockkAll()
    }
    
    @OptIn(ExperimentalCoroutinesApi::class)
    @Test
    fun test() = runTest {
        testScope.launch {
                 // call your suspend method here
                Utils.testSuspendMethod()
            )
          //add here assertions for the method
        }

    }

}
寄离 2025-02-12 03:36:32

这也许是因为有一个Coroutine在您的视图模型或他的依赖性(例如存储库)中永无止境。您可以尝试几件事:

  1. 我不确定现在在Android Studio上是否可以使用Coroutines调试器,但是如果是,您可以在所有测试功能之后放置一个断点,并使用它来检查哪个Coroutine是哪个Coroutine是仍在运行。

  2. 如果您使用的是coroutinescope(sustaSorjob() + dispatchers.default),请检查视图模型及其依赖项。如果是,请确保使用60年代完成的悬挂功能。如果您使用的是myFlow.oneach {}。

  3. 不要用测试范围替换外部范围的调度程序(例如coroutinescope(sustepisorjob() + mytestDisPatcher))),否则,测试执行将阻止执行您的暂停功能和执行悬挂功能只有在测试结束后才会返回,这可能会导致这种无限的测试行为或如果您进行仪器测试,则会卡住屏幕。另外,将范围和调度器放在构造函数上,因此在测试中,您可以控制使用哪个。

    @Test
    fun flowViewModelTesting1() = runTest {

        /** Given **/
        val viewModel = HomeViewModel(
            orderRepository = FakeOrderRepository(
                // ioDispatcher may execute execute the task of call the server
                // or in the fake version wait 1,000 ms and return a mock
                ioDispatcher =  TestDispatcher(this.testScheduler),
                // External scope may launch a order update
                externalScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
            )
        )

        /** When **/
        val result = viewModel.homeUiState.drop(1).first()

        /** Then **/
        assertEquals(
            expected = true,
            actual = result.myBoolean
        )
    }

This is maybe because there is a coroutine that never ends in your view model or in his dependencies (like the repository). There is a couple of things you can try:

  1. I am not sure if coroutines debugger is available on Android Studio right now, but if it is you can put a breakpoint after all your test functions and use it to check which coroutine is still running.

  2. Check in your view model and its dependencies if you are using an external scope like CoroutineScope(SupervisorJob() + Dispatchers.Default). If you are, make sure you are using with a suspend function that finishes in 60s. If you are using to observe a value like in myFlow.onEach{}.launchIn(externalScope) it may be that because the job is still running.

  3. Don't replace the dispatchers of your external scope with the test scope (like in CoroutineScope(SupervisorJob() + myTestDispatcher)), otherwise the test execution will block the execution of your suspend functions and the suspend function will only return after your test is over, which may cause this infinite testing behavior or a screen being stucked if you are making instrumented tests. Also, put your scopes and dispatchers on the constructor, so in tests you can control which one is being used.

    @Test
    fun flowViewModelTesting1() = runTest {

        /** Given **/
        val viewModel = HomeViewModel(
            orderRepository = FakeOrderRepository(
                // ioDispatcher may execute execute the task of call the server
                // or in the fake version wait 1,000 ms and return a mock
                ioDispatcher =  TestDispatcher(this.testScheduler),
                // External scope may launch a order update
                externalScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
            )
        )

        /** When **/
        val result = viewModel.homeUiState.drop(1).first()

        /** Then **/
        assertEquals(
            expected = true,
            actual = result.myBoolean
        )
    }
许一世地老天荒 2025-02-12 03:36:32

就我而言,我只需要在流动过程中所要求的函数中所需的嘲笑参数。

Function1()->Function2()//this Function2 needs mocked variables.
every { mockBanner.bannerKey } returns “mockAnswer”

在这里,我忘了在function2中模拟bannerkey,这解决了此问题。

In my case, I just was required to mockk parameters that were required in the functions I called during the flow.

Function1()->Function2()//this Function2 needs mocked variables.
every { mockBanner.bannerKey } returns “mockAnswer”

Here I had Forgot to mock bannerKey in Function2,that resolved this issue.

挖鼻大婶 2025-02-12 03:36:32

如果您使用的是Kotlinx-coroutines-Test-JVM:1.7或更高版本,则可以作为参数传递超时。默认参数为10秒。

/**
 * The default timeout to use when running a test.
 */
internal val DEFAULT_TIMEOUT = 10.seconds

...
public fun runTest(
    context: CoroutineContext = EmptyCoroutineContext,
    timeout: Duration = DEFAULT_TIMEOUT,
    testBody: suspend TestScope.() -> Unit
): TestResult 

因此,您可以将参数添加到runtest

fun `test load`() = runTest(timeout = 60.seconds) {
...
}

If you are using kotlinx-coroutines-test-jvm:1.7 or higher you can pass the timeout as an argument. The default argument is 10 seconds.

/**
 * The default timeout to use when running a test.
 */
internal val DEFAULT_TIMEOUT = 10.seconds

...
public fun runTest(
    context: CoroutineContext = EmptyCoroutineContext,
    timeout: Duration = DEFAULT_TIMEOUT,
    testBody: suspend TestScope.() -> Unit
): TestResult 

So you can add the argument to runTest

fun `test load`() = runTest(timeout = 60.seconds) {
...
}
愁以何悠 2025-02-12 03:36:32

此错误的大多数时间原因是您尚未完成观察者变量调用。

在测试案例呼叫的结尾处finish()方法在您的观察者变量上。

observerVariable.finish()

Most of the time reason for this error is that you have not finish your observer variable call.

At the end of your test case call finish() method on your observer variable.

observerVariable.finish()
极度宠爱 2025-02-12 03:36:32

您不使用testDisPatcher。您应该将其传递给runtest喜欢:

runTest(UnconfinedTestDispatcher()) {}

https://developer.android.android.android.android.android .com/kotlin/coroutines/test

You're not using the testDispatcher. You should pass it to runTest like:

runTest(UnconfinedTestDispatcher()) {}

https://developer.android.com/kotlin/coroutines/test

幸福还没到 2025-02-12 03:36:32

当您使用runtest时,您默认使用standardTestDisPatcher,这意味着它不会立即运行。
您必须从一直使用的视图模型

替换

runTest {

testDispather.runTest {

同一调度程序中启动测试,请尝试一下。如果不检查您的ViewModel代码,我不能分享太多。

When you use runTest, so you're using StandardTestDispatcher by default that means it won't immediately run.
You have to launch your test from the same dispatcher that you have been using for a ViewModel

Replace

runTest {

with

testDispather.runTest {

it should work, give it a try. I can't share much without checking your viewmodel code.

左秋 2025-02-12 03:36:32

所有这些答案都是错误的,您正在寻找dispatchTimeOutms

@Test
fun `my long-running test`() = runTest(
  dispatchTimeoutMs = 60000L, // Increase this number as needed, this is the default which is 60 seconds in milliseconds
  context = testDispatcher // also, still pass in a TestDispatcher
) {
  // ... Rest of your long-running test
}

通常,您应该尝试使测试运行更快或测试较小的事物,但是我需要每次一次这样做。

All of these answers are wrong, you are looking for dispatchTimeoutMs:

@Test
fun `my long-running test`() = runTest(
  dispatchTimeoutMs = 60000L, // Increase this number as needed, this is the default which is 60 seconds in milliseconds
  context = testDispatcher // also, still pass in a TestDispatcher
) {
  // ... Rest of your long-running test
}

Normally, you should try to make your test run faster, or test smaller things, but I have needed to do this from time-to-time.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文