Skip to content

Commit

Permalink
Add more tests for TestFormula and fix a bug. (#376)
Browse files Browse the repository at this point in the history
  • Loading branch information
Laimiux authored Aug 16, 2024
1 parent 58d5431 commit ac33937
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ fun <Input : Any, State : Any, Output : Any> withSnapshot(

fun <Input, Output> IFormula<Input, Output>.testFormula(
initialOutput: Output,
key: (Input) -> Any? = { null }
): TestFormula<Input, Output> {
return TestFormula(initialOutput, key)
return TestFormula(initialOutput, key = { this.key(it) })
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@ abstract class TestFormula<Input, Output> :
}

private fun getByKey(key: Any?): Value<Input, Output> {
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"
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ChildFormula.Input, ChildFormula.Button>.() -> Unit) = apply {
childFormula.implementation.assertion()
}

fun assertOutput(on: ParentFormula.Output.() -> Unit) = apply {
observer.output(on)
}

class ParentFormula(
private val childFormula: ChildFormula
) : Formula<Unit, ParentFormula.State, ParentFormula.Output>() {

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<Unit, State>.evaluate(): Evaluation<Output> {
return Evaluation(
output = Output(
name = state.name,
button = context.child(childFormula, ChildFormula.Input(
name = state.name,
onChangeName = context.onEvent<String> {
transition(state.copy(name = it))
}
))
)
)
}
}

interface ChildFormula : IFormula<ChildFormula.Input, ChildFormula.Button> {

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 {}
)
}
}
Original file line number Diff line number Diff line change
@@ -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<Unit, ParentFormula.Output, ParentFormula>

@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<Unit, ParentFormula.State, ParentFormula.Output>() {
@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<Unit, State>.evaluate(): Evaluation<Output> {
return Evaluation(
output = Output(
name = state.name,
button = context.child(childFormula, ChildFormula.Input(
name = state.name,
onChangeName = context.onEvent<String> {
transition(state.copy(name = it))
}
))
)
)
fail("Should not happen")
} catch (e: Exception) {
assertThat(e).hasMessageThat().startsWith("Formula is not running")
}
}

interface ChildFormula : IFormula<ChildFormula.Input, ChildFormula.Button> {
@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("") }
}
}
}

0 comments on commit ac33937

Please sign in to comment.