Skip to content

Commit fd1d185

Browse files
authored
Merge pull request #1285 from square/ray/unit-test-frozen
Make `testRender()` mimic `RealRenderContext.frozen`
2 parents 221f16c + bdf2bf4 commit fd1d185

File tree

2 files changed

+68
-3
lines changed

2 files changed

+68
-3
lines changed

workflow-testing/src/main/java/com/squareup/workflow1/testing/RealRenderTester.kt

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ internal class RealRenderTester<PropsT, StateT, OutputT, RenderingT>(
103103
}
104104
}
105105

106+
private var frozen = false
107+
106108
private var explicitWorkerExpectationsRequired: Boolean = false
107109
private var explicitSideEffectExpectationsRequired: Boolean = false
108110
private val stateAndOutput: Pair<StateT, WorkflowOutput<OutputT>?> by lazy {
@@ -151,12 +153,13 @@ internal class RealRenderTester<PropsT, StateT, OutputT, RenderingT>(
151153
expectSideEffect(description = "unexpected side effect", exactMatch = false) { true }
152154
}
153155

156+
frozen = false
154157
// Clone the expectations to run a "dry" render pass.
155158
val noopContext = deepCloneForRender()
156159
workflow.render(props, state, RenderContext(noopContext, workflow))
157-
158-
workflow.render(props, state, RenderContext(this, workflow))
159-
.also(block)
160+
val rendering = workflow.render(props, state, RenderContext(this, workflow))
161+
frozen = true
162+
block(rendering)
160163

161164
// Ensure all exact matches were consumed.
162165
val unconsumedExactMatches = expectations.filter {
@@ -184,6 +187,7 @@ internal class RealRenderTester<PropsT, StateT, OutputT, RenderingT>(
184187
key: String,
185188
handler: (ChildOutputT) -> WorkflowAction<PropsT, StateT, OutputT>
186189
): ChildRenderingT {
190+
checkNotFrozen { "renderChild(${child.identifier})" }
187191
val identifierPair = Pair(child.identifier, key)
188192
require(identifierPair !in renderedChildren) {
189193
"Expected keys to be unique for ${child.identifier}: key=\"$key\""
@@ -244,6 +248,7 @@ internal class RealRenderTester<PropsT, StateT, OutputT, RenderingT>(
244248
key: String,
245249
sideEffect: suspend CoroutineScope.() -> Unit
246250
) {
251+
checkNotFrozen { "runningSideEffect($key)" }
247252
require(key !in ranSideEffects) { "Expected side effect keys to be unique: \"$key\"" }
248253
ranSideEffects += key
249254

@@ -279,6 +284,7 @@ internal class RealRenderTester<PropsT, StateT, OutputT, RenderingT>(
279284
vararg inputs: Any?,
280285
calculation: () -> ResultT
281286
): ResultT {
287+
checkNotFrozen { "remember($key)" }
282288
val mapKey = TestRememberKey(key, resultType, inputs.asList())
283289
check(rememberSet.add(mapKey)) {
284290
"Expected combination of key, inputs and result type to be unique: \"$key\""
@@ -297,6 +303,12 @@ internal class RealRenderTester<PropsT, StateT, OutputT, RenderingT>(
297303
}
298304

299305
override fun send(value: WorkflowAction<PropsT, StateT, OutputT>) {
306+
if (!frozen) {
307+
throw UnsupportedOperationException(
308+
"Expected sink to not be sent to until after the render pass. " +
309+
"Received action: ${value.debuggingName}"
310+
)
311+
}
300312
checkNoOutputs()
301313
check(processedAction == null) {
302314
"Tried to send action to sink after another action was already processed:\n" +
@@ -363,6 +375,11 @@ internal class RealRenderTester<PropsT, StateT, OutputT, RenderingT>(
363375
expectationsWithOutputs.joinToString(separator = "\n") { " $it" }
364376
}
365377
}
378+
379+
private fun checkNotFrozen(reason: () -> String = { "" }) = check(!frozen) {
380+
"RenderContext cannot be used after render method returns" +
381+
"${reason().takeUnless { it.isBlank() }?.let { " ($it)" }}"
382+
}
366383
}
367384

368385
internal fun createRenderChildInvocation(

workflow-testing/src/test/java/com/squareup/workflow1/testing/RealRenderTesterTest.kt

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.squareup.workflow1.Sink
66
import com.squareup.workflow1.Snapshot
77
import com.squareup.workflow1.StatefulWorkflow
88
import com.squareup.workflow1.StatelessWorkflow
9+
import com.squareup.workflow1.StatelessWorkflow.RenderContext
910
import com.squareup.workflow1.Worker
1011
import com.squareup.workflow1.Workflow
1112
import com.squareup.workflow1.WorkflowAction
@@ -17,6 +18,7 @@ import com.squareup.workflow1.action
1718
import com.squareup.workflow1.asWorker
1819
import com.squareup.workflow1.contraMap
1920
import com.squareup.workflow1.identifier
21+
import com.squareup.workflow1.remember
2022
import com.squareup.workflow1.renderChild
2123
import com.squareup.workflow1.rendering
2224
import com.squareup.workflow1.runningWorker
@@ -1271,6 +1273,52 @@ internal class RealRenderTesterTest {
12711273
assertEquals(2, renderCount)
12721274
}
12731275

1276+
@Test fun `enforces frozen failures on late renderChild call`() {
1277+
lateinit var capturedContext: StatelessWorkflow<Unit, Nothing, Unit>.RenderContext
1278+
val workflow = Workflow.stateless { capturedContext = this }
1279+
1280+
workflow.testRender(Unit)
1281+
.render()
1282+
1283+
assertFailsWith<IllegalStateException> {
1284+
capturedContext.renderChild(workflow)
1285+
}
1286+
}
1287+
1288+
@Test fun `enforces frozen failures on late runningSideEffect call`() {
1289+
lateinit var capturedContext: StatelessWorkflow<Unit, Nothing, Unit>.RenderContext
1290+
val workflow = Workflow.stateless { capturedContext = this }
1291+
1292+
workflow.testRender(Unit)
1293+
.render()
1294+
1295+
assertFailsWith<IllegalStateException> {
1296+
capturedContext.runningSideEffect(key = "fnord") {}
1297+
}
1298+
}
1299+
1300+
@Test fun `enforces frozen failures on late remember call`() {
1301+
lateinit var capturedContext: StatelessWorkflow<Unit, Nothing, Unit>.RenderContext
1302+
val workflow = Workflow.stateless { capturedContext = this }
1303+
1304+
workflow.testRender(Unit)
1305+
.render()
1306+
1307+
assertFailsWith<IllegalStateException> {
1308+
capturedContext.remember(key = "fnord") {}
1309+
}
1310+
}
1311+
1312+
@Test fun `enforces failures on send while rendering`() {
1313+
val workflow = Workflow.stateless<Unit, Nothing, Unit> {
1314+
actionSink.send(action("fnord") {})
1315+
}
1316+
1317+
assertFailsWith<UnsupportedOperationException> {
1318+
workflow.testRender(Unit).render()
1319+
}
1320+
}
1321+
12741322
@OptIn(WorkflowExperimentalApi::class)
12751323
@Test
12761324
fun `testRender with SessionWorkflow throws exception`() {

0 commit comments

Comments
 (0)