<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/flake-free-tests---part-2-hardening-turbine-tests-nschjybyv" -->

---
title: Flake-Free Tests - Part 2: Hardening Turbine Tests
description: Testing Kotlin Flows with Turbine gets flaky unless you account for dispatcher timing and hot-vs-cold stream behavior. State-holder flows like StateFlow should...
canonical: https://daily.dev/posts/flake-free-tests---part-2-hardening-turbine-tests-nschjybyv
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: Flake-Free Tests - Part 2: Hardening Turbine Tests | daily.dev
og:description: Testing Kotlin Flows with Turbine gets flaky unless you account for dispatcher timing and hot-vs-cold stream behavior. State-holder flows like StateFlow should...
og:url: https://daily.dev/posts/flake-free-tests---part-2-hardening-turbine-tests-nschjybyv
og:image: https://api.daily.dev/og/posts/NSChJyBYv.png
og:image:alt: Flake-Free Tests - Part 2: Hardening Turbine Tests
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.

# Flake-Free Tests - Part 2: Hardening Turbine Tests

**[Touchlab](https://daily.dev/sources/touchlab)** · 15 min read · 0 upvotes · 0 comments

## Summary

Testing Kotlin Flows with Turbine gets flaky unless you account for dispatcher timing and hot-vs-cold stream behavior. State-holder flows like StateFlow should be asserted on their final `.value` rather than every intermediate emission, since conflation can skip states like Loading. Sync points like `runCurrent()` prevent races: expectMostRecentItem() returned stale values in nearly every run until a sync point made results identical across runs. Turbine's timeout is wall-clock, so a virtual 10-second delay passed a 500ms timeout while a real 800ms delay failed it. The post uses Turbine 1.2.0 and kotlinx-coroutines 1.10.1, and recommends ending tests with `cancelAndIgnoreRemainingEvents()` when a flow may emit unwanted items, to avoid flaky buffered-event failures.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://touchlab.co/flake-free-flow-tests-pt2-turbine>

## Questions this post answers

### Should I assert every emission or just the final value when testing a StateFlow with Turbine?

Assert the final value using `.value` rather than every intermediate emission. StateFlow conflates rapid updates, so intermediate states like Loading can be skipped entirely, especially when using UnconfinedTestDispatcher where the producer can run straight through to the final state before Turbine observes the in-between value. Only assert an intermediate state if it is part of the contract, and make its timing deterministic with StandardTestDispatcher.

_Developers debugging flaky StateFlow assertions can find patterns like this one on daily.dev._

### Why does my Turbine test hang or miss events when using stateIn or shareIn with StandardTestDispatcher?

With replay = 0 on a StandardTestDispatcher, the sharing coroutine is queued and hasn't started collecting yet, so any value emitted from a hot upstream during that gap is dropped and Turbine never sees it. For stateIn, only the last value makes it through across Eagerly, Lazily, and WhileSubscribed variants, so awaiting an earlier state like Loading hangs. Fix it with runCurrent() before the emission or by running the sharing on an Unconfined dispatcher.

_Anyone wiring up shareIn or stateIn in coroutine tests can track fixes like this on daily.dev._

### Why do my Turbine tests fail intermittently even though the logic seems correct?

Leftover buffered or late events after the test block ends cause intermittent failures. An item still buffered when the test block closes fails every run, while a late event arriving just after failed only 3 out of 300 runs in one setup, which is the flaky pattern. Calling cancelAndIgnoreRemainingEvents() before ending the test eliminated these failures entirely across runs.

_Teams chasing flaky coroutine tests can keep up with fixes like this via daily.dev._

## Similar posts on daily.dev

- [Flake-Free Flow Tests in Kotlin - Hardening Coroutine Tests](https://daily.dev/posts/flake-free-flow-tests-in-kotlin---hardening-coroutine-tests-hadvvxnk2) · Touchlab · 1 upvotes · 1 comments
- [Your Coroutine Tests Are Flaky Because You Have Two Clocks Running](https://daily.dev/posts/your-coroutine-tests-are-flaky-because-you-have-two-clocks-running-nwzhmolh7) · ProAndroidDev · 2 upvotes · 0 comments
- [How Emissions Work in Kotlin Flows and Channels](https://daily.dev/posts/how-emissions-work-in-kotlin-flows-and-channels-uhe2jnnv3) · ProAndroidDev · 0 upvotes · 0 comments
- [Everything Is a Source — Part 1](https://daily.dev/posts/everything-is-a-source-part-1-uixeghosh) · Medium · 1 upvotes · 0 comments

---

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

[View this post on daily.dev](https://daily.dev/posts/flake-free-tests---part-2-hardening-turbine-tests-nschjybyv)

```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":"Flake-Free Tests - Part 2: Hardening Turbine Tests","url":"https://daily.dev/posts/flake-free-tests---part-2-hardening-turbine-tests-nschjybyv","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/flake-free-tests---part-2-hardening-turbine-tests-nschjybyv"},"datePublished":"2026-10-30T20:33:10.516Z","dateModified":"2026-10-30T20:33:42.546Z","description":"Testing Kotlin Flows with Turbine gets flaky unless you account for dispatcher timing and hot-vs-cold stream behavior. State-holder flows like StateFlow should...","image":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/1abc3a23b37d03f0914680ab54cebb0c?_a=AQAEuop","thumbnailUrl":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/1abc3a23b37d03f0914680ab54cebb0c?_a=AQAEuop","isAccessibleForFree":true,"articleSection":"Touchlab","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":"Touchlab","logo":"https://media.daily.dev/image/upload/s--CQ0u6GwB--/f_auto,q_auto/v1785654902/logos/touchlab?_a=BAMAMicg0","url":"https://daily.dev/sources/touchlab"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/flake-free-tests---part-2-hardening-turbine-tests-nschjybyv","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":0},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":"kotlin","timeRequired":"PT15M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"Touchlab","item":"https://daily.dev/sources/touchlab"},{"@type":"ListItem","position":3,"name":"Flake-Free Tests - Part 2: Hardening Turbine Tests"}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/flake-free-tests---part-2-hardening-turbine-tests-nschjybyv#faq","mainEntity":[{"@type":"Question","name":"Should I assert every emission or just the final value when testing a StateFlow with Turbine?","acceptedAnswer":{"@type":"Answer","text":"Assert the final value using `.value` rather than every intermediate emission. StateFlow conflates rapid updates, so intermediate states like Loading can be skipped entirely, especially when using UnconfinedTestDispatcher where the producer can run straight through to the final state before Turbine observes the in-between value. Only assert an intermediate state if it is part of the contract, and make its timing deterministic with StandardTestDispatcher. Developers debugging flaky StateFlow assertions can find patterns like this one on daily.dev."}},{"@type":"Question","name":"Why does my Turbine test hang or miss events when using stateIn or shareIn with StandardTestDispatcher?","acceptedAnswer":{"@type":"Answer","text":"With replay = 0 on a StandardTestDispatcher, the sharing coroutine is queued and hasn't started collecting yet, so any value emitted from a hot upstream during that gap is dropped and Turbine never sees it. For stateIn, only the last value makes it through across Eagerly, Lazily, and WhileSubscribed variants, so awaiting an earlier state like Loading hangs. Fix it with runCurrent() before the emission or by running the sharing on an Unconfined dispatcher. Anyone wiring up shareIn or stateIn in coroutine tests can track fixes like this on daily.dev."}},{"@type":"Question","name":"Why do my Turbine tests fail intermittently even though the logic seems correct?","acceptedAnswer":{"@type":"Answer","text":"Leftover buffered or late events after the test block ends cause intermittent failures. An item still buffered when the test block closes fails every run, while a late event arriving just after failed only 3 out of 300 runs in one setup, which is the flaky pattern. Calling cancelAndIgnoreRemainingEvents() before ending the test eliminated these failures entirely across runs. Teams chasing flaky coroutine tests can keep up with fixes like this via daily.dev."}}]}
```

