Snapshots of the development version are available in the Central Portal Snapshots repository.
```kotlin
repositories {
maven {
url = uri("https://central.sonatype.com/repository/maven-snapshots/")
}
}
dependencies {
testImplementation("app.cash.turbine:turbine:1.3.0-SNAPSHOT")
}
```
While Turbine's own API is stable, we are currently forced to depend on an unstable API from
kotlinx.coroutines test artifact: `UnconfinedTestDispatcher`. Without this usage of Turbine with
`runTest` would break. It's possible for future coroutine library updates to alter the behavior of
this library as a result. We will make every effort to ensure behavioral stability as well until this
API dependency is stabilized (tracking [issue #132](https://github.com/cashapp/turbine/issues/132)).
## Usage
A `Turbine` is a thin wrapper over a `Channel` with an API designed for testing.
You can call `awaitItem()` to suspend and wait for an item to be sent to the `Turbine`:
```kotlin
assertEquals("one", turbine.awaitItem())
```
...`awaitComplete()` to suspend until the `Turbine` completes without an exception:
```kotlin
turbine.awaitComplete()
```
...or `awaitError()` to suspend until the `Turbine` completes with a `Throwable`.
```kotlin
assertEquals("broken!", turbine.awaitError().message)
```
If `await*` is called and nothing happens, `Turbine` will timeout and fail instead of hanging.
When you are done with a `Turbine`, you can clean up by calling `cancel()` to terminate any backing coroutines.
Finally, you can assert that all events were consumed by calling `ensureAllEventsConsumed()`.
### Single Flow
The simplest way to create and run a `Turbine` is produce one from a `Flow`.
To test a single `Flow`, call the `test` extension:
```kotlin
someFlow.test {
// Validation code here!
}
```
`test` launches a new coroutine, calls `someFlow.collect`, and feeds the results into a `Turbine`.
Then it calls the validation block, passing in the read-only `ReceiveTurbine` interface as a receiver:
```kotlin
flowOf("one").test {
assertEquals("one", awaitItem())
awaitComplete()
}
```
When the validation block is complete, `test` cancels the coroutine and calls `ensureAllEventsConsumed()`.
### Multiple Flows
To test multiple flows, assign each `Turbine` to a separate `val` by calling `testIn` instead:
```kotlin
runTest {
turbineScope {
val turbine1 = flowOf(1).testIn(backgroundScope)
val turbine2 = flowOf(2).testIn(backgroundScope)
assertEquals(1, turbine1.awaitItem())
assertEquals(2, turbine2.awaitItem())
turbine1.awaitComplete()
turbine2.awaitComplete()
}
}
```
Like `test`, `testIn` produces a `ReceiveTurbine`.
`ensureAllEventsConsumed()` will be invoked when the calling coroutine completes.
`testIn` cannot automatically clean up its coroutine, so it is up to you to ensure that the running flow terminates.
Use `runTest`'s `backgroundScope`, and it will take care of this automatically.
Otherwise, make sure to call one of the following methods before the end of your scope:
* `cancel()`
* `awaitComplete()`
* `awaitError()`
Otherwise, your test will hang.
### Consuming All Events
Failing to consume all events before the end of a flow-based `Turbine`'s validation block will fail your test:
```kotlin
flowOf("one", "two").test {
assertEquals("one", awaitItem())
}
```
```
Exception in thread "main" AssertionError:
Unconsumed events found:
- Item(two)
- Complete
```
The same goes for `testIn`, but at the end of the calling coroutine:
```kotlin
runTest {
turbineScope {
val turbine = flowOf("one", "two").testIn(backgroundScope)
assertEquals("one", turbine.awaitItem())
}
}
```
```
Exception in thread "main" AssertionError:
Unconsumed events found:
- Item(two)
- Complete
```
Received events can be explicitly ignored, however.
```kotlin
flowOf("one", "two").test {
assertEquals("one", awaitItem())
cancelAndIgnoreRemainingEvents()
}
```
Additionally, we can receive the most recent emitted item and ignore the previous ones.
```kotlin
flowOf("one", "two", "three")
.map {
delay(100)
it
}
.test {
// 0 - 100ms -> no emission yet
// 100ms - 200ms -> "one" is emitted
// 200ms - 300ms -> "two" is emitted
// 300ms - 400ms -> "three" is emitted
delay(250)
assertEquals("two", expectMostRecentItem())
cancelAndIgnoreRemainingEvents()
}
```
### Flow Termination
Flow termination events (exceptions and completion) are exposed as events which must be consumed for validation.
So, for example, throwing a `RuntimeException` inside of your `flow` will not throw an exception in your test.
It will instead produce a Turbine error event:
```kotlin
flow { throw RuntimeException("broken!") }.test {
assertEquals("broken!", awaitError().message)
}
```
Failure to consume an error will result in the same unconsumed event exception as above, but
with the exception added as the cause so that the full stacktrace is available.
```kotlin
flow