From ac3393768704aef9cfce70ecf6c3177bc5fe0174 Mon Sep 17 00:00:00 2001 From: Laimonas Turauskas Date: Fri, 16 Aug 2024 18:01:14 +0300 Subject: [PATCH] Add more tests for TestFormula and fix a bug. (#376) --- .../instacart/formula/test/TestExtensions.kt | 3 +- .../com/instacart/formula/test/TestFormula.kt | 4 +- .../formula/test/TestFormulaRobot.kt | 72 +++++++++ .../instacart/formula/test/TestFormulaTest.kt | 144 +++++++++++------- 4 files changed, 163 insertions(+), 60 deletions(-) create mode 100644 formula-test/src/test/java/com/instacart/formula/test/TestFormulaRobot.kt diff --git a/formula-test/src/main/java/com/instacart/formula/test/TestExtensions.kt b/formula-test/src/main/java/com/instacart/formula/test/TestExtensions.kt index 76bb37a2..61c993cb 100644 --- a/formula-test/src/main/java/com/instacart/formula/test/TestExtensions.kt +++ b/formula-test/src/main/java/com/instacart/formula/test/TestExtensions.kt @@ -42,7 +42,6 @@ fun withSnapshot( fun IFormula.testFormula( initialOutput: Output, - key: (Input) -> Any? = { null } ): TestFormula { - return TestFormula(initialOutput, key) + return TestFormula(initialOutput, key = { this.key(it) }) } \ No newline at end of file diff --git a/formula-test/src/main/java/com/instacart/formula/test/TestFormula.kt b/formula-test/src/main/java/com/instacart/formula/test/TestFormula.kt index 89e241af..8a7014d8 100644 --- a/formula-test/src/main/java/com/instacart/formula/test/TestFormula.kt +++ b/formula-test/src/main/java/com/instacart/formula/test/TestFormula.kt @@ -119,8 +119,8 @@ abstract class TestFormula : } private fun getByKey(key: Any?): Value { - return requireNotNull(stateMap.entries.firstOrNull { it.key == key }?.value) { - val existingKeys = stateMap.entries.map { it.key } + return requireNotNull(stateMap.entries.firstOrNull { it.value.key == key }?.value) { + val existingKeys = stateMap.entries.map { it.value.key } "Formula for $key is not running, there are $existingKeys running" } } diff --git a/formula-test/src/test/java/com/instacart/formula/test/TestFormulaRobot.kt b/formula-test/src/test/java/com/instacart/formula/test/TestFormulaRobot.kt new file mode 100644 index 00000000..047b9d19 --- /dev/null +++ b/formula-test/src/test/java/com/instacart/formula/test/TestFormulaRobot.kt @@ -0,0 +1,72 @@ +package com.instacart.formula.test + +import com.instacart.formula.Evaluation +import com.instacart.formula.Formula +import com.instacart.formula.IFormula +import com.instacart.formula.Snapshot + +class TestFormulaRobot { + + private val childFormula: FakeChildFormula = FakeChildFormula() + private val parentFormula: ParentFormula = ParentFormula(childFormula) + private val observer = parentFormula.test() + + + fun start() = apply { + observer.input(Unit) + } + + fun withTestFormula(assertion: TestFormula.() -> Unit) = apply { + childFormula.implementation.assertion() + } + + fun assertOutput(on: ParentFormula.Output.() -> Unit) = apply { + observer.output(on) + } + + class ParentFormula( + private val childFormula: ChildFormula + ) : Formula() { + + data class State(val name: String) + + data class Output( + val name: String, + val button: ChildFormula.Button + ) + + override fun initialState(input: Unit): State = State(name = "") + + override fun Snapshot.evaluate(): Evaluation { + return Evaluation( + output = Output( + name = state.name, + button = context.child(childFormula, ChildFormula.Input( + name = state.name, + onChangeName = context.onEvent { + transition(state.copy(name = it)) + } + )) + ) + ) + } + } + + interface ChildFormula : IFormula { + + data class Input( + val name: String, + val onChangeName: (newName: String) -> Unit + ) + + class Button(val onNameChanged: (String) -> Unit) + + override fun key(input: Input): Any? = "child-key" + } + + class FakeChildFormula : ChildFormula { + override val implementation = testFormula( + initialOutput = ChildFormula.Button {} + ) + } +} \ No newline at end of file diff --git a/formula-test/src/test/java/com/instacart/formula/test/TestFormulaTest.kt b/formula-test/src/test/java/com/instacart/formula/test/TestFormulaTest.kt index d39c1263..4321d973 100644 --- a/formula-test/src/test/java/com/instacart/formula/test/TestFormulaTest.kt +++ b/formula-test/src/test/java/com/instacart/formula/test/TestFormulaTest.kt @@ -1,81 +1,113 @@ package com.instacart.formula.test import com.google.common.truth.Truth.assertThat -import com.instacart.formula.Evaluation -import com.instacart.formula.Formula -import com.instacart.formula.IFormula -import com.instacart.formula.Snapshot -import org.junit.Before +import junit.framework.Assert.fail import org.junit.Test class TestFormulaTest { - lateinit var childFormula: FakeChildFormula - lateinit var parentFormula: ParentFormula - lateinit var subject: TestFormulaObserver - - @Before fun setup() { - childFormula = FakeChildFormula() - parentFormula = ParentFormula(childFormula) - subject = ParentFormula(childFormula = childFormula).test(Unit) + @Test fun `assert running count is zero when formula is not running`() { + TestFormulaRobot() + .withTestFormula { assertRunningCount(0) } + .start() + .withTestFormula { assertRunningCount(1) } } - @Test fun `trigger listener using child input`() { - subject - .apply { - childFormula.implementation.input { onChangeName("my name") } - } - .output { - assertThat(name).isEqualTo("my name") - } - } + @Test fun `output throws an exception when no test formula is running`() { + try { + TestFormulaRobot() + .withTestFormula { output(TestFormulaRobot.ChildFormula.Button(onNameChanged = {})) } - @Test fun `input passed to formula`() { - childFormula.implementation.input { - assertThat(name).isEqualTo("") + fail("Should not happen") + } catch (e: Exception) { + assertThat(e).hasMessageThat().startsWith("Formula is not running") } } - class ParentFormula( - private val childFormula: ChildFormula - ) : Formula() { + @Test fun `output emits new output to the parent`() { + val newOutput = TestFormulaRobot.ChildFormula.Button(onNameChanged = {}) + TestFormulaRobot() + .start() + .withTestFormula { output(newOutput) } + .assertOutput { assertThat(this.button).isEqualTo(newOutput) } + } + + @Test fun `output with key throws an exception when test formula matching key is not running`() { + val newOutput = TestFormulaRobot.ChildFormula.Button(onNameChanged = {}) + try { + TestFormulaRobot() + .start() + .withTestFormula { + output(key = "random-key", newOutput) + } - data class State(val name: String) + fail("Should not happen") + } catch (e: Exception) { + assertThat(e).hasMessageThat().startsWith("Formula for random-key is not running, there are [child-key] running") + } + } - data class Output( - val name: String, - val button: ChildFormula.Button - ) + @Test fun `output with key emits new output to the parent when key matches`() { + val newOutput = TestFormulaRobot.ChildFormula.Button(onNameChanged = {}) + TestFormulaRobot() + .start() + .withTestFormula { output(key = "child-key", newOutput) } + .assertOutput { assertThat(this.button).isEqualTo(newOutput) } + } - override fun initialState(input: Unit): State = State(name = "") + @Test fun `input() throw an exception when no formulas are running`() { + try { + TestFormulaRobot() + .withTestFormula { + input { onChangeName("my name") } + } - override fun Snapshot.evaluate(): Evaluation { - return Evaluation( - output = Output( - name = state.name, - button = context.child(childFormula, ChildFormula.Input( - name = state.name, - onChangeName = context.onEvent { - transition(state.copy(name = it)) - } - )) - ) - ) + fail("Should not happen") + } catch (e: Exception) { + assertThat(e).hasMessageThat().startsWith("Formula is not running") } } - interface ChildFormula : IFormula { + @Test fun `input() works as expected when formula is running`() { + TestFormulaRobot() + .start() + .withTestFormula { + input { onChangeName("my name") } + } + .assertOutput { + assertThat(name).isEqualTo("my name") + } + } - data class Input( - val name: String, - val onChangeName: (newName: String) -> Unit - ) + @Test fun `input() throws an error when NO formula that matches the key provided is running`() { + try { + TestFormulaRobot() + .start() + .withTestFormula { + input(key = "random-key") { onChangeName("my name") } + } - class Button(val onNameChanged: (String) -> Unit) + fail("Should not happen") + } catch (e: Exception) { + assertThat(e).hasMessageThat().startsWith("Formula for random-key is not running, there are [child-key] running") + } } - class FakeChildFormula : ChildFormula { - override val implementation = testFormula( - initialOutput = ChildFormula.Button {} - ) + @Test fun `input() works as expected when formula that matches the key provided is running`() { + TestFormulaRobot() + .start() + .withTestFormula { + input(key = "child-key") { onChangeName("my name") } + } + .assertOutput { + assertThat(name).isEqualTo("my name") + } + } + + @Test fun `input passed to formula`() { + TestFormulaRobot() + .start() + .withTestFormula { + input { assertThat(name).isEqualTo("") } + } } }