| name | workflow-integration-testing |
| description | Write integration tests for Workflows using renderForTest and WorkflowTurbine. Use when testing full workflow runtime behavior, async operations, state changes over time, output emissions, multi-step user flows, or when user mentions "integration test", "renderForTest", or "WorkflowTurbine". |
Workflow Integration Testing with renderForTest
Write integration tests that run a full workflow runtime using renderForTest and
WorkflowTurbine. Unlike unit tests with testRender, these tests execute real workers,
real child workflows, and real async behavior.
When to Use
Use testRender (unit tests) when... | Use renderForTest (integration tests) when... |
|---|
| Testing a single render pass | Testing multi-step user flows |
| Faking all children and workers | Running real children and workers |
| Verifying render logic in isolation | Testing async behavior end-to-end |
| Fast, focused tests | Testing state changes over time |
Core API
renderForTest is an extension function on StatefulWorkflow that:
- Starts a real workflow runtime
- Provides a
WorkflowTurbine for consuming renderings, outputs, and snapshots
- Automatically manages scope and cleanup when the test block completes
Basic Pattern
@Test fun `workflow handles user flow`() {
MyWorkflow.renderForTest {
val first = awaitNextRendering()
assertEquals("Welcome", first.title)
first.onButtonClicked()
val second = awaitNextRendering()
assertEquals("Loading...", second.title)
}
}
renderForTest Variants
Unit Props (most common)
MyWorkflow.renderForTest {
val rendering = awaitNextRendering()
}
With Props
MyWorkflow.renderForTest(
props = MutableStateFlow(MyProps("initial")).asStateFlow()
) {
val rendering = awaitNextRendering()
}
From Specific State
MyWorkflow.renderForTestFromStateWith(
initialState = MyState.Error("something broke")
) {
val rendering = awaitNextRendering()
assertTrue(rendering.isError)
}
From Specific State with Props
MyWorkflow.renderForTestFromStateWith(
props = MutableStateFlow(MyProps("test")).asStateFlow(),
initialState = MyState.Loaded("data")
) {
val rendering = awaitNextRendering()
}
For Any Workflow (not just StatefulWorkflow)
myWorkflow.renderForTestForStartWith {
val rendering = awaitNextRendering()
}
myWorkflow.renderForTestForStartWith(
props = MutableStateFlow(myProps).asStateFlow()
) {
val rendering = awaitNextRendering()
}
WorkflowTurbine API
Inside a renderForTest block, you have access to a WorkflowTurbine with these methods:
Renderings
val rendering = awaitNextRendering()
val first = firstRendering
skipRenderings(3)
val loaded = awaitNextRenderingSatisfying { it.isLoaded }
val title = awaitNext(
precondition = { it.isLoaded },
map = { it.title },
satisfying = { isNotEmpty() }
)
Outputs
val output = awaitNextOutput()
assertEquals(MyOutput.Completed, output)
Snapshots
val snapshot = awaitNextSnapshot()
val first = firstSnapshot
Testing Patterns
Multi-Step User Flow
@Test fun `login flow from welcome to todo list`() {
RootWorkflow.renderForTest {
val welcome = awaitNextRendering()
assertEquals("Welcome", welcome.title)
welcome.onLogIn("Alice")
val todoList = awaitNextRendering()
assertEquals("Alice", todoList.username)
}
}
Testing Output Emissions
@Test fun `workflow emits output on completion`() {
MyWorkflow.renderForTest {
val rendering = awaitNextRendering()
rendering.onComplete()
val output = awaitNextOutput()
assertEquals(MyOutput.Finished, output)
}
}
Testing Props Changes
@Test fun `workflow responds to prop changes`() {
val props = MutableStateFlow(MyProps("initial"))
MyWorkflow.renderForTest(props = props.asStateFlow()) {
val first = awaitNextRendering()
assertEquals("initial", first.title)
props.value = MyProps("updated")
val second = awaitNextRendering()
assertEquals("updated", second.title)
}
}
Starting from Error State
@Test fun `retry from error state`() {
MyWorkflow.renderForTestFromStateWith(
initialState = MyState.Error("Network error")
) {
val errorRendering = awaitNextRendering()
assertTrue(errorRendering.isError)
assertEquals("Network error", errorRendering.errorMessage)
errorRendering.onRetry()
val loadingRendering = awaitNextRendering()
assertTrue(loadingRendering.isLoading)
}
}
Skipping Intermediate Renderings
@Test fun `final state after multiple transitions`() {
MyWorkflow.renderForTest {
val first = awaitNextRendering()
first.onStart()
skipRenderings(3)
val final = awaitNextRendering()
assertTrue(final.isComplete)
}
}
Waiting for Specific Rendering
@Test fun `wait for loaded state`() {
MyWorkflow.renderForTest {
val first = awaitNextRendering()
first.onLoadData()
val loaded = awaitNextRenderingSatisfying { rendering ->
rendering.isLoaded
}
assertEquals("Data loaded", loaded.message)
}
}
WorkflowTestParams
Customize test behavior with WorkflowTestParams:
MyWorkflow.renderForTest(
testParams = WorkflowTestParams(
startFrom = StartFresh,
checkRenderIdempotence = true,
runtimeConfig = null
)
) {
}
Configuration
Custom Timeout
Default timeout is 60 seconds. Override for long-running or time-sensitive tests:
MyWorkflow.renderForTest(
testTimeout = 10_000L
) {
}
Custom Coroutine Context
MyWorkflow.renderForTest(
coroutineContext = StandardTestDispatcher()
) {
}
Output Callback
Handle outputs outside the turbine (e.g., for logging):
MyWorkflow.renderForTest(
onOutput = { output -> println("Got output: $output") }
) {
}
Best Practices
- Use integration tests for multi-step flows — unit tests (
testRender) are better for
isolated render logic
awaitNextRendering() includes the first rendering — the first call returns the
synchronously-produced initial rendering
- No manual cleanup needed —
renderForTest manages scope and cancellation automatically
- Don't mix event triggers with child/worker outputs in the same render — only one source
of action per render pass
- Use
awaitNextRenderingSatisfying when intermediate renderings are unpredictable
- Use
skipRenderings when you know how many intermediate states to skip
Required Imports
import com.squareup.workflow1.testing.renderForTest
import com.squareup.workflow1.testing.renderForTestFromStateWith
import com.squareup.workflow1.testing.renderForTestForStartWith
import com.squareup.workflow1.testing.WorkflowTestParams
import com.squareup.workflow1.testing.WorkflowTestParams.StartMode.StartFresh
import com.squareup.workflow1.testing.WorkflowTestParams.StartMode.StartFromState
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
Deprecated APIs — Do NOT Use
launchForTestingFromStartWith — replaced by renderForTest
launchForTestingWith — replaced by renderForTest
launchForTestingFromStateWith — replaced by renderForTestFromStateWith
WorkflowTestRuntime — replaced by WorkflowTurbine
Documentation