<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/your-coroutine-tests-are-flaky-because-you-have-two-clocks-running-nwzhmolh7" -->

---
title: Your Coroutine Tests Are Flaky Because You Have Two...
description: Flaky coroutine tests often stem from two clocks running simultaneously: the virtual clock provided by TestScope/TestCoroutineScheduler and a real clock when...
canonical: https://daily.dev/posts/your-coroutine-tests-are-flaky-because-you-have-two-clocks-running-nwzhmolh7
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: Your Coroutine Tests Are Flaky Because You Have Two Clocks Running | daily.dev
og:description: Flaky coroutine tests often stem from two clocks running simultaneously: the virtual clock provided by TestScope/TestCoroutineScheduler and a real clock when...
og:url: https://daily.dev/posts/your-coroutine-tests-are-flaky-because-you-have-two-clocks-running-nwzhmolh7
og:image: https://api.daily.dev/og/posts/nWzHmolh7.png
og:image:alt: Your Coroutine Tests Are Flaky Because You Have Two Clocks Running
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Your Coroutine Tests Are Flaky Because You Have Two Clocks Running

**[ProAndroidDev](https://daily.dev/sources/pand)** · 8 min read · 2 upvotes · 0 comments

## Summary

Flaky coroutine tests often stem from two clocks running simultaneously: the virtual clock provided by TestScope/TestCoroutineScheduler and a real clock when code switches to a real dispatcher like Dispatchers.IO. The fix is dependency-injecting dispatchers so tests can substitute a TestDispatcher, plus using Dispatchers.setMain() to replace the Main dispatcher for ViewModel coroutines. The piece contrasts StandardTestDispatcher (queued, controlled execution) with UnconfinedTestDispatcher (eager execution), and warns against overusing advanceUntilIdle(), which only controls the fake clock and can hang forever on infinite producers. Recommends runCurrent(), advanceTimeBy(), and backgroundScope.launch() as more precise alternatives.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://proandroiddev.com/your-coroutine-tests-are-flaky-because-you-have-two-clocks-running-dfc2382909af>

## Questions this post answers

### Why do my Kotlin coroutine tests pass locally but fail intermittently on CI?

Flaky coroutine tests are typically caused by production code switching to a real dispatcher, like Dispatchers.IO via withContext, while the test runs on a virtual clock inside runTest. The test scheduler cannot see or control work happening on the real thread pool, so assertions can race against unfinished async work, passing on fast machines but failing under CI load.

_Developers chasing intermittent CI failures can track coroutine testing gotchas like this via daily.dev._

### What is the difference between StandardTestDispatcher and UnconfinedTestDispatcher in kotlinx-coroutines-test?

StandardTestDispatcher queues coroutine work and requires explicit calls like runCurrent() or advanceUntilIdle() to execute it, which is useful for testing debounces, timeouts, or in-between states. UnconfinedTestDispatcher instead starts coroutines eagerly and runs until the first suspension point, making it the simpler default when execution order control isn't needed.

_Anyone choosing between coroutine test dispatchers can find comparisons like this on daily.dev._

### Why does my coroutine test using advanceUntilIdle() hang forever?

advanceUntilIdle() runs everything queued on the test scheduler until no suspension points remain, but an infinite producer, such as a flow with a while(true) polling loop and delay, always has more queued work, so the function never returns. The kotlinx-coroutines maintainers document in issue #3919 that once a coroutine hands off to a real dispatcher, the test dispatcher loses control entirely.

_Debugging hung coroutine tests gets easier when developers share fixes like this through daily.dev._

## Similar posts on daily.dev

- [Your ViewModel Tests Shouldn’t Need Android](https://daily.dev/posts/your-viewmodel-tests-shouldn-t-need-android-mcqlipvqw) · ProAndroidDev · 1 upvotes · 1 comments
- [Coroutines, the Right Way: Where Your Money Code Actually Runs](https://daily.dev/posts/coroutines-the-right-way-where-your-money-code-actually-runs-hckmygkn4) · Medium · 0 upvotes · 0 comments
- [You’re Spawning Too Many Coroutines \(And Your App Is Paying the Price\)](https://daily.dev/posts/you-re-spawning-too-many-coroutines-and-your-app-is-paying-the-price--cnpg6wg9f) · ProAndroidDev · 1 upvotes · 1 comments
- [ViewModelScope Internals: A Deep Dive into Android’s Threading Magic](https://daily.dev/posts/viewmodelscope-internals-a-deep-dive-into-android-s-threading-magic-bbobu0rok) · droidcon · 0 upvotes · 0 comments

---

Tags: [#testing](https://daily.dev/tags/testing), [#android](https://daily.dev/tags/android), [#kotlin](https://daily.dev/tags/kotlin)

[View this post on daily.dev](https://daily.dev/posts/your-coroutine-tests-are-flaky-because-you-have-two-clocks-running-nwzhmolh7)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"Your Coroutine Tests Are Flaky Because You Have Two Clocks Running","url":"https://daily.dev/posts/your-coroutine-tests-are-flaky-because-you-have-two-clocks-running-nwzhmolh7","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/your-coroutine-tests-are-flaky-because-you-have-two-clocks-running-nwzhmolh7"},"datePublished":"2026-08-16T17:44:13.877Z","dateModified":"2026-09-13T19:44:44.437Z","description":"Flaky coroutine tests often stem from two clocks running simultaneously: the virtual clock provided by TestScope/TestCoroutineScheduler and a real clock when...","image":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/ea0ecd5b99d067511b8b360e7b65b66b?_a=AQAEuop","thumbnailUrl":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/ea0ecd5b99d067511b8b360e7b65b66b?_a=AQAEuop","isAccessibleForFree":true,"articleSection":"ProAndroidDev","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"ProAndroidDev","logo":"https://media.daily.dev/image/upload/t_logo,f_auto/v1/logos/656c1eb1f1ef4f91936e86fc16545114","url":"https://daily.dev/sources/pand"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/your-coroutine-tests-are-flaky-because-you-have-two-clocks-running-nwzhmolh7","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":2},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":"testing,android,kotlin","timeRequired":"PT8M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"ProAndroidDev","item":"https://daily.dev/sources/pand"},{"@type":"ListItem","position":3,"name":"Your Coroutine Tests Are Flaky Because You Have Two Clocks Running"}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/your-coroutine-tests-are-flaky-because-you-have-two-clocks-running-nwzhmolh7#faq","mainEntity":[{"@type":"Question","name":"Why do my Kotlin coroutine tests pass locally but fail intermittently on CI?","acceptedAnswer":{"@type":"Answer","text":"Flaky coroutine tests are typically caused by production code switching to a real dispatcher, like Dispatchers.IO via withContext, while the test runs on a virtual clock inside runTest. The test scheduler cannot see or control work happening on the real thread pool, so assertions can race against unfinished async work, passing on fast machines but failing under CI load. Developers chasing intermittent CI failures can track coroutine testing gotchas like this via daily.dev."}},{"@type":"Question","name":"What is the difference between StandardTestDispatcher and UnconfinedTestDispatcher in kotlinx-coroutines-test?","acceptedAnswer":{"@type":"Answer","text":"StandardTestDispatcher queues coroutine work and requires explicit calls like runCurrent() or advanceUntilIdle() to execute it, which is useful for testing debounces, timeouts, or in-between states. UnconfinedTestDispatcher instead starts coroutines eagerly and runs until the first suspension point, making it the simpler default when execution order control isn't needed. Anyone choosing between coroutine test dispatchers can find comparisons like this on daily.dev."}},{"@type":"Question","name":"Why does my coroutine test using advanceUntilIdle() hang forever?","acceptedAnswer":{"@type":"Answer","text":"advanceUntilIdle() runs everything queued on the test scheduler until no suspension points remain, but an infinite producer, such as a flow with a while(true) polling loop and delay, always has more queued work, so the function never returns. The kotlinx-coroutines maintainers document in issue #3919 that once a coroutine hands off to a real dispatcher, the test dispatcher loses control entirely. Debugging hung coroutine tests gets easier when developers share fixes like this through daily.dev."}}]}
```

