[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-7413":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":16,"subscribersCount":16,"size":16,"stars1d":16,"stars7d":16,"stars30d":15,"stars90d":16,"forks30d":16,"starsTrendScore":16,"compositeScore":17,"rankGlobal":10,"rankLanguage":10,"license":18,"archived":19,"fork":19,"defaultBranch":20,"hasWiki":19,"hasPages":21,"topics":22,"createdAt":10,"pushedAt":10,"updatedAt":23,"readmeContent":24,"aiSummary":25,"trendingCount":16,"starSnapshotCount":16,"syncStatus":26,"lastSyncTime":27,"discoverSource":28},7413,"turbine","cashapp\u002Fturbine","cashapp","A testing library for kotlinx.coroutines Flow","https:\u002F\u002Fcashapp.github.io\u002Fturbine\u002Fdocs\u002F1.x\u002F",null,"Kotlin",2836,131,21,12,0,28.36,"Apache License 2.0",false,"trunk",true,[],"2026-06-12 02:01:39","# Turbine\n\nTurbine is a small testing library for kotlinx.coroutines\n[`Flow`](https:\u002F\u002Fkotlin.github.io\u002Fkotlinx.coroutines\u002Fkotlinx-coroutines-core\u002Fkotlinx.coroutines.flow\u002F-flow\u002F).\n\n```kotlin\nflowOf(\"one\", \"two\").test {\n  assertEquals(\"one\", awaitItem())\n  assertEquals(\"two\", awaitItem())\n  awaitComplete()\n}\n```\n\n> A turbine is a rotary mechanical device that extracts energy from a fluid flow and converts it into useful work.\n>\n> – [Wikipedia](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FTurbine)\n\n## Download\n\n```kotlin\nrepositories {\n  mavenCentral()\n}\ndependencies {\n  testImplementation(\"app.cash.turbine:turbine:1.2.1\")\n}\n```\n\n\u003Cdetails>\n\u003Csummary>Snapshots of the development version are available in the Central Portal Snapshots repository.\u003C\u002Fsummary>\n\u003Cp>\n\n```kotlin\nrepositories {\n  maven {\n    url = uri(\"https:\u002F\u002Fcentral.sonatype.com\u002Frepository\u002Fmaven-snapshots\u002F\")\n  }\n}\ndependencies {\n  testImplementation(\"app.cash.turbine:turbine:1.3.0-SNAPSHOT\")\n}\n```\n\n\u003C\u002Fp>\n\u003C\u002Fdetails>\n\nWhile Turbine's own API is stable, we are currently forced to depend on an unstable API from\nkotlinx.coroutines test artifact: `UnconfinedTestDispatcher`. Without this usage of Turbine with\n`runTest` would break. It's possible for future coroutine library updates to alter the behavior of\nthis library as a result. We will make every effort to ensure behavioral stability as well until this\nAPI dependency is stabilized (tracking [issue #132](https:\u002F\u002Fgithub.com\u002Fcashapp\u002Fturbine\u002Fissues\u002F132)).\n\n## Usage\n\nA `Turbine` is a thin wrapper over a `Channel` with an API designed for testing.\n\nYou can call `awaitItem()` to suspend and wait for an item to be sent to the `Turbine`:\n\n```kotlin\nassertEquals(\"one\", turbine.awaitItem())\n```\n\n...`awaitComplete()` to suspend until the `Turbine` completes without an exception:\n\n```kotlin\nturbine.awaitComplete()\n```\n\n...or `awaitError()` to suspend until the `Turbine` completes with a `Throwable`.\n\n```kotlin\nassertEquals(\"broken!\", turbine.awaitError().message)\n```\n\nIf `await*` is called and nothing happens, `Turbine` will timeout and fail instead of hanging.\n\nWhen you are done with a `Turbine`, you can clean up by calling `cancel()` to terminate any backing coroutines.\nFinally, you can assert that all events were consumed by calling `ensureAllEventsConsumed()`.\n\n\n### Single Flow\n\nThe simplest way to create and run a `Turbine` is produce one from a `Flow`.\nTo test a single `Flow`, call the `test` extension:\n\n```kotlin\nsomeFlow.test {\n  \u002F\u002F Validation code here!\n}\n```\n\n`test` launches a new coroutine, calls `someFlow.collect`, and feeds the results into a `Turbine`.\nThen it calls the validation block, passing in the read-only `ReceiveTurbine` interface as a receiver:\n\n```kotlin\nflowOf(\"one\").test {\n  assertEquals(\"one\", awaitItem())\n  awaitComplete()\n}\n```\n\nWhen the validation block is complete, `test` cancels the coroutine and calls `ensureAllEventsConsumed()`.\n\n### Multiple Flows\n\nTo test multiple flows, assign each `Turbine` to a separate `val` by calling `testIn` instead:\n\n```kotlin\nrunTest {\n  turbineScope {\n    val turbine1 = flowOf(1).testIn(backgroundScope)\n    val turbine2 = flowOf(2).testIn(backgroundScope)\n    assertEquals(1, turbine1.awaitItem())\n    assertEquals(2, turbine2.awaitItem())\n    turbine1.awaitComplete()\n    turbine2.awaitComplete()\n  }\n}\n```\n\nLike `test`, `testIn` produces a `ReceiveTurbine`.\n`ensureAllEventsConsumed()` will be invoked when the calling coroutine completes.\n\n`testIn` cannot automatically clean up its coroutine, so it is up to you to ensure that the running flow terminates.\nUse `runTest`'s `backgroundScope`, and it will take care of this automatically.\nOtherwise, make sure to call one of the following methods before the end of your scope:\n\n* `cancel()`\n* `awaitComplete()`\n* `awaitError()`\n\nOtherwise, your test will hang.\n\n### Consuming All Events\n\nFailing to consume all events before the end of a flow-based `Turbine`'s validation block will fail your test:\n\n```kotlin\nflowOf(\"one\", \"two\").test {\n  assertEquals(\"one\", awaitItem())\n}\n```\n```\nException in thread \"main\" AssertionError:\n  Unconsumed events found:\n   - Item(two)\n   - Complete\n```\n\nThe same goes for `testIn`, but at the end of the calling coroutine:\n\n```kotlin\nrunTest {\n  turbineScope {\n    val turbine = flowOf(\"one\", \"two\").testIn(backgroundScope)\n    turbine.assertEquals(\"one\", awaitItem())\n  }\n}\n```\n```\nException in thread \"main\" AssertionError:\n  Unconsumed events found:\n   - Item(two)\n   - Complete\n```\n\nReceived events can be explicitly ignored, however.\n\n```kotlin\nflowOf(\"one\", \"two\").test {\n  assertEquals(\"one\", awaitItem())\n  cancelAndIgnoreRemainingEvents()\n}\n```\n\nAdditionally, we can receive the most recent emitted item and ignore the previous ones.\n\n```kotlin\nflowOf(\"one\", \"two\", \"three\")\n  .map {\n    delay(100)\n    it\n  }\n  .test {\n    \u002F\u002F 0 - 100ms -> no emission yet\n    \u002F\u002F 100ms - 200ms -> \"one\" is emitted\n    \u002F\u002F 200ms - 300ms -> \"two\" is emitted\n    \u002F\u002F 300ms - 400ms -> \"three\" is emitted\n    delay(250)\n    assertEquals(\"two\", expectMostRecentItem())\n    cancelAndIgnoreRemainingEvents()\n  }\n```\n\n\n### Flow Termination\n\nFlow termination events (exceptions and completion) are exposed as events which must be consumed for validation.\nSo, for example, throwing a `RuntimeException` inside of your `flow` will not throw an exception in your test.\nIt will instead produce a Turbine error event:\n\n```kotlin\nflow { throw RuntimeException(\"broken!\") }.test {\n  assertEquals(\"broken!\", awaitError().message)\n}\n```\n\nFailure to consume an error will result in the same unconsumed event exception as above, but\nwith the exception added as the cause so that the full stacktrace is available.\n\n```kotlin\nflow\u003CNothing> { throw RuntimeException(\"broken!\") }.test { }\n```\n```\napp.cash.turbine.TurbineAssertionError: Unconsumed events found:\n - Error(RuntimeException)\n\tat app\u002F\u002Fapp.cash.turbine.ChannelTurbine.ensureAllEventsConsumed(Turbine.kt:215)\n  ... 80 more\nCaused by: java.lang.RuntimeException: broken!\n\tat example.MainKt$main$1.invokeSuspend(FlowTest.kt:652)\n\t... 105 more\n```\n\n### Standalone Turbines\n\nIn addition to `ReceiveTurbine`s created from flows, standalone `Turbine`s can be used to communicate with test code outside of a flow.\nUse them everywhere, and you might never need `runCurrent()` again.\nHere's an example of how to use `Turbine()` in a fake:\n\n```kotlin\nclass FakeNavigator : Navigator {\n  val goTos = Turbine\u003CScreen>()\n\n  override fun goTo(screen: Screen) {\n    goTos.add(screen)\n  }\n}\n```\n```kotlin\nrunTest {\n  val navigator = FakeNavigator()\n  val events: Flow\u003CUiEvent> =\n    MutableSharedFlow\u003CUiEvent>(extraBufferCapacity = 50)\n  val models: Flow\u003CUiModel> =\n    makePresenter(navigator).present(events)\n  models.test {\n    assertEquals(UiModel(title = \"Hi there\"), awaitItem())\n    events.emit(UiEvent.Close)\n    assertEquals(Screens.Back, navigator.goTos.awaitItem())\n  }\n}\n```\n\n### Standalone Turbine Compat APIs\n\nTo support codebases with a mix of coroutines and non-coroutines code, standalone `Turbine` includes non-suspending compat APIs.\nAll the `await` methods have equivalent `take` methods that are non-suspending:\n\n```kotlin\nval navigator = FakeNavigator()\nval events: PublishRelay\u003CUiEvent> = PublishRelay.create()\n\nval models: Observable\u003CUiModel> =\n  makePresenter(navigator).present(events)\nval testObserver = models.test()\ntestObserver.assertValue(UiModel(title = \"Hi there\"))\nevents.accept(UiEvent.Close)\nassertEquals(Screens.Back, navigator.goTos.takeItem())\n```\n\nUse `takeItem()` and friends, and `Turbine` behaves like simple queue; use `awaitItem()` and friends, and it's a `Turbine`.\n\nThese methods should only be used from a non-suspending context.\nOn JVM platforms, they will throw when used from a suspending context.\n\n### Asynchronicity and Turbine\n\nFlows are asynchronous by default. Your flow is collected concurrently by Turbine alongside your test code.\n\nHandling this asynchronicity works the same way with Turbine as it does in production coroutines code:\ninstead of using tools like `runCurrent()` to \"push\" an asynchronous flow along, `Turbine`'s `awaitItem()`, `awaitComplete()`, and `awaitError()` \"pull\" them along by parking until a new event is ready.\n\n```kotlin\nchannelFlow {\n  withContext(IO) {\n    Thread.sleep(100)\n    send(\"item\")\n  }\n}.test {\n  assertEquals(\"item\", awaitItem())\n  awaitComplete()\n}\n```\n\nYour validation code may run concurrently with the flow under test, but Turbine puts it in the driver's seat as much as possible:\n`test` will end when your validation block is done executing, implicitly cancelling the flow under test.\n\n```kotlin\nchannelFlow {\n  withContext(IO) {\n    repeat(10) {\n      Thread.sleep(200)\n      send(\"item $it\")\n    }\n  }\n}.test {\n  assertEquals(\"item 0\", awaitItem())\n  assertEquals(\"item 1\", awaitItem())\n  assertEquals(\"item 2\", awaitItem())\n}\n```\n\nFlows can also be explicitly canceled at any point.\n\n```kotlin\nchannelFlow {\n  withContext(IO) {\n    repeat(10) {\n      Thread.sleep(200)\n      send(\"item $it\")\n    }\n  }\n}.test {\n  Thread.sleep(700)\n  cancel()\n\n  assertEquals(\"item 0\", awaitItem())\n  assertEquals(\"item 1\", awaitItem())\n  assertEquals(\"item 2\", awaitItem())\n}\n```\n\n### Names\n\nTurbines can be named to improve error feedback.\nPass in a `name` to `test`, `testIn`, or `Turbine()`, and it will be included in any errors that are thrown:\n\n```kotlin\nrunTest {\n  turbineScope {\n    val turbine1 = flowOf(1).testIn(backgroundScope, name = \"turbine 1\")\n    val turbine2 = flowOf(2).testIn(backgroundScope, name = \"turbine 2\")\n    turbine1.awaitComplete()\n    turbine2.awaitComplete()\n  }\n}\n```\n```\nExpected complete for turbine 1 but found Item(1)\napp.cash.turbine.TurbineAssertionError: Expected complete for turbine 1 but found Item(1)\n\tat app\u002F\u002Fapp.cash.turbine.ChannelKt.unexpectedEvent(channel.kt:258)\n\tat app\u002F\u002Fapp.cash.turbine.ChannelKt.awaitComplete(channel.kt:226)\n\tat app\u002F\u002Fapp.cash.turbine.ChannelKt$awaitComplete$1.invokeSuspend(channel.kt)\n\tat app\u002F\u002Fkotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)\n\t...\n```\n\n### Order of Execution & Shared Flows\n\nShared flows are sensitive to order of execution.\nCalling `emit` before calling `collect` will drop the emitted value:\n\n```kotlin\nval mutableSharedFlow = MutableSharedFlow\u003CInt>(replay = 0)\nmutableSharedFlow.emit(1)\nmutableSharedFlow.test {\n  assertEquals(awaitItem(), 1)\n}\n```\n```\nNo value produced in 1s\njava.lang.AssertionError: No value produced in 1s\n\tat app.cash.turbine.ChannelKt.awaitEvent(channel.kt:90)\n\tat app.cash.turbine.ChannelKt$awaitEvent$1.invokeSuspend(channel.kt)\n\t(Coroutine boundary)\n\tat kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt$runTestCoroutine$2.invokeSuspend(TestBuilders.kt:212)\n```\n\nTurbine's `test` and `testIn` methods guarantee that the flow under test will run up to the first suspension point before proceeding.\nSo calling `test` on a shared flow _before_ emitting will not drop:\n\n```kotlin\nval mutableSharedFlow = MutableSharedFlow\u003CInt>(replay = 0)\nmutableSharedFlow.test {\n  mutableSharedFlow.emit(1)\n  assertEquals(awaitItem(), 1)\n}\n```\n\nIf your code collects on shared flows, ensure that it does so promptly to have a lovely experience.\n\nThe shared flow types Kotlin currently provides are:\n* `MutableStateFlow`\n* `StateFlow`\n* `MutableSharedFlow`\n* `SharedFlow`\n\n### Timeouts\n\nTurbine applies a timeout whenever it waits for an event.\nThis is a wall clock time timeout that ignores `runTest`'s virtual clock time.\n\nThe default timeout length is three seconds. This can be overridden by passing a timeout duration to `test`:\n\n```kotlin\nflowOf(\"one\", \"two\").test(timeout = 10.milliseconds) {\n  ...\n}\n```\n\nThis timeout will be used for all Turbine-related calls inside the validation block.\n\nYou can also override the timeout for Turbines created with `testIn` and `Turbine()`:\n\n```kotlin\nval standalone = Turbine\u003CString>(timeout = 10.milliseconds)\nval flow = flowOf(\"one\").testIn(\n  scope = backgroundScope,\n  timeout = 10.milliseconds,\n)\n```\n\nThese timeout overrides only apply to the `Turbine` on which they were applied.\n\nFinally, you can also change the timeout for a whole block of code using `withTurbineTimeout`:\n\n```kotlin\nwithTurbineTimeout(10.milliseconds) {\n  ...\n}\n```\n\n### Channel Extensions\n\nMost of Turbine's APIs are implemented as extensions on `Channel`.\nThe more limited API surface of `Turbine` is usually preferable, but these extensions are also available as public APIs if you need them.\n\n# License\n\n    Copyright 2018 Square, Inc.\n\n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n\n       http:\u002F\u002Fwww.apache.org\u002Flicenses\u002FLICENSE-2.0\n\n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n","Turbine 是一个专为 kotlinx.coroutines Flow 设计的测试库。其核心功能包括提供简洁的API来验证Flow中的数据项、完成状态或错误，支持单个或多个Flow的测试，并能在测试完成后自动清理资源。通过使用`awaitItem()`、`awaitComplete()`和`awaitError()`等方法，开发者可以方便地对异步数据流进行断言，确保所有事件都被正确处理。Turbine适用于需要对基于Kotlin协程构建的应用程序中的数据流逻辑进行单元测试的场景，特别是那些涉及复杂异步操作的应用。该库依赖于kotlinx.coroutines的某些不稳定API，但团队致力于保持其行为的一致性。",2,"2026-06-11 03:12:11","top_language"]