diff --git a/formula-test/src/main/java/com/instacart/formula/test/RxJavaFormulaTestDelegate.kt b/formula-test/src/main/java/com/instacart/formula/test/RxJavaFormulaTestDelegate.kt index 0692ea290..b4fdc4112 100644 --- a/formula-test/src/main/java/com/instacart/formula/test/RxJavaFormulaTestDelegate.kt +++ b/formula-test/src/main/java/com/instacart/formula/test/RxJavaFormulaTestDelegate.kt @@ -13,8 +13,8 @@ import io.reactivex.rxjava3.subjects.BehaviorSubject class RxJavaFormulaTestDelegate>( override val formula: FormulaT, isValidationEnabled: Boolean = true, - inspector: Inspector? = null, - dispatcher: Dispatcher? = null, + inspector: Inspector?, + dispatcher: Dispatcher?, ) : FormulaTestDelegate { private val runtimeConfig = RuntimeConfig( isValidationEnabled = isValidationEnabled, diff --git a/formula-test/src/main/java/com/instacart/formula/test/TestActionObserver.kt b/formula-test/src/main/java/com/instacart/formula/test/TestActionObserver.kt index 857545eda..5a6a1f0ac 100644 --- a/formula-test/src/main/java/com/instacart/formula/test/TestActionObserver.kt +++ b/formula-test/src/main/java/com/instacart/formula/test/TestActionObserver.kt @@ -27,6 +27,10 @@ class TestActionObserver(private val action: Action) { * provide a [Cancelable]. */ fun cancel() { - cancelation!!.cancel() + val cancelable = cancelation ?: run { + throw IllegalStateException("Action did not return a cancelable.") + } + + cancelable.cancel() } } diff --git a/formula-test/src/main/java/com/instacart/formula/test/TestCallback.kt b/formula-test/src/main/java/com/instacart/formula/test/TestCallback.kt index 5d4895d00..3699aee2e 100644 --- a/formula-test/src/main/java/com/instacart/formula/test/TestCallback.kt +++ b/formula-test/src/main/java/com/instacart/formula/test/TestCallback.kt @@ -8,8 +8,8 @@ class TestCallback : () -> Unit { } fun assertTimesCalled(times: Int) { - assert(invocationCount == times) { - "Expected: $times, was: $invocationCount" + if (invocationCount != times) { + throw AssertionError("Expected: $times, was: $invocationCount") } } } diff --git a/formula-test/src/main/java/com/instacart/formula/test/TestFormulaObserver.kt b/formula-test/src/main/java/com/instacart/formula/test/TestFormulaObserver.kt index 8958d8373..cbaa2ce99 100644 --- a/formula-test/src/main/java/com/instacart/formula/test/TestFormulaObserver.kt +++ b/formula-test/src/main/java/com/instacart/formula/test/TestFormulaObserver.kt @@ -28,7 +28,7 @@ class TestFormulaObserver Unit) = apply { + fun output(assert: Output.() -> Unit) = apply { ensureFormulaIsRunning() assertNoErrors() // Check before interaction assert(values().last()) @@ -39,8 +39,9 @@ class TestFormulaObserver : Listener { fun assertTimesCalled(times: Int) { val timesCalled = values.size - assert(timesCalled == times) { - "Expected: $times, was: $timesCalled" + if (timesCalled != times) { + throw AssertionError("Expected: $times, was: $timesCalled") } } } diff --git a/formula-test/src/main/java/com/instacart/formula/test/TestRuntimeExtensions.kt b/formula-test/src/main/java/com/instacart/formula/test/TestRuntimeExtensions.kt index ab0049342..44222f5c2 100644 --- a/formula-test/src/main/java/com/instacart/formula/test/TestRuntimeExtensions.kt +++ b/formula-test/src/main/java/com/instacart/formula/test/TestRuntimeExtensions.kt @@ -19,21 +19,5 @@ fun > F.test( return TestFormulaObserver(delegate) } -/** - * An extension function to create a [TestFormulaObserver] for a [IFormula] instance. - * - * @param initialInput Input passed to [IFormula]. - */ -fun > F.test( - initialInput: Input, - isValidationEnabled: Boolean = true, - inspector: Inspector? = null, - dispatcher: Dispatcher? = null, -): TestFormulaObserver { - return test(isValidationEnabled, inspector, dispatcher).apply { - input(initialInput) - } -} - fun Action.test() = TestActionObserver(this) diff --git a/formula-test/src/test/java/com/instacart/formula/test/CountingInspectorTest.kt b/formula-test/src/test/java/com/instacart/formula/test/CountingInspectorTest.kt new file mode 100644 index 000000000..a2d77b987 --- /dev/null +++ b/formula-test/src/test/java/com/instacart/formula/test/CountingInspectorTest.kt @@ -0,0 +1,75 @@ +package com.instacart.formula.test + +import com.google.common.truth.Truth +import com.instacart.formula.Evaluation +import com.instacart.formula.Snapshot +import com.instacart.formula.StatelessFormula +import org.junit.Test + +class CountingInspectorTest { + + @Test + fun `assertEvaluationCount throws exception when count does not match`() { + val inspector = CountingInspector() + inspector.assertEvaluationCount(0) + + val result = runCatching { inspector.assertEvaluationCount(5) } + Truth.assertThat(result.exceptionOrNull()).hasMessageThat().contains( + "Evaluation count does not match - count: 0, expected: 5" + ) + } + + @Test + fun `assertEvaluationCount with types throws exception when count does not match`() { + val inspector = CountingInspector() + inspector.assertEvaluationCount(MyFormula::class, 0) + inspector.onEvaluateFinished(MyFormula::class, null, true) + inspector.assertEvaluationCount(MyFormula::class, 1) + + val result = runCatching { inspector.assertEvaluationCount(MyFormula::class, 5) } + Truth.assertThat(result.exceptionOrNull()).hasMessageThat().contains( + "Evaluation count does not match - count: 1, expected: 5" + ) + } + + @Test + fun `assertRunCount throws exception when count does not match`() { + val inspector = CountingInspector() + inspector.assertRunCount(0) + + val result = runCatching { inspector.assertRunCount(5) } + Truth.assertThat(result.exceptionOrNull()).hasMessageThat().contains( + "Run count does not match - count: 0, expected: 5" + ) + } + + @Test + fun `assertActionsStarted throws exception when count does not match`() { + val inspector = CountingInspector() + inspector.assertActionsStarted(0) + + val result = runCatching { inspector.assertActionsStarted(5) } + Truth.assertThat(result.exceptionOrNull()).hasMessageThat().contains( + "Actions started count does not match - count: 0, expected: 5" + ) + } + + @Test + fun `assertStateTransitions throws exception when count does not match`() { + val inspector = CountingInspector() + inspector.assertStateTransitions(MyFormula::class, 0) + inspector.onStateChanged(MyFormula::class, null, null, null) + inspector.assertStateTransitions(MyFormula::class, 1) + + val result = runCatching { inspector.assertStateTransitions(MyFormula::class, 5) } + Truth.assertThat(result.exceptionOrNull()).hasMessageThat().contains( + "State transition count does not match - count: 1, expected: 5" + ) + } + + class MyFormula : StatelessFormula() { + override fun Snapshot.evaluate(): Evaluation { + return Evaluation(Unit) + } + } +} \ No newline at end of file diff --git a/formula-test/src/test/java/com/instacart/formula/test/TestActionObserverTest.kt b/formula-test/src/test/java/com/instacart/formula/test/TestActionObserverTest.kt new file mode 100644 index 000000000..d08cfc8f8 --- /dev/null +++ b/formula-test/src/test/java/com/instacart/formula/test/TestActionObserverTest.kt @@ -0,0 +1,61 @@ +package com.instacart.formula.test + +import com.google.common.truth.Truth +import com.google.common.truth.Truth.assertThat +import com.instacart.formula.Action +import com.instacart.formula.Cancelable +import org.junit.Test +import java.lang.IllegalStateException + +class TestActionObserverTest { + + @Test fun `assert values success`() { + multipleValueStream().test().assertValues(1, 2) + } + + @Test fun `assert value fails due to different size`() { + val result = runCatching { multipleValueStream().test().assertValues(1) } + assertThat(result.exceptionOrNull()).isInstanceOf(AssertionError::class.java) + } + + @Test fun `assert value fails due to different value`() { + val result = runCatching { multipleValueStream().test().assertValues(1, 5) } + assertThat(result.exceptionOrNull()).isInstanceOf(AssertionError::class.java) + } + + @Test fun values() { + val values = multipleValueStream().test().values() + assertThat(values).hasSize(2) + } + + @Test fun `cancel throws exception if action does not provide cancelable`() { + val result = kotlin.runCatching { multipleValueStream().test().cancel() } + assertThat(result.exceptionOrNull()).hasMessageThat().contains( + "Action did not return a cancelable." + ) + } + + @Test fun `cancel invokes cancelable`() { + var cancelableCalled = 0 + val action = object : Action { + override fun start(send: (String) -> Unit): Cancelable { + return Cancelable { cancelableCalled += 1 } + } + + override fun key(): Any? = null + } + + action.test().cancel() + assertThat(cancelableCalled).isEqualTo(1) + } + + private fun multipleValueStream() = object : Action { + override fun start(send: (Int) -> Unit): Cancelable? { + send(1) + send(2) + return null + } + + override fun key(): Any = Unit + } +} diff --git a/formula-test/src/test/java/com/instacart/formula/test/TestCallbackTest.kt b/formula-test/src/test/java/com/instacart/formula/test/TestCallbackTest.kt new file mode 100644 index 000000000..a59ff61c0 --- /dev/null +++ b/formula-test/src/test/java/com/instacart/formula/test/TestCallbackTest.kt @@ -0,0 +1,20 @@ +package com.instacart.formula.test + +import com.google.common.truth.Truth +import org.junit.Test + +class TestCallbackTest { + + @Test + fun `assertTimesCalled throws an exception when count does not match`() { + val callback = TestCallback() + callback.assertTimesCalled(0) + callback.invoke() + callback.assertTimesCalled(1) + + val result = kotlin.runCatching { callback.assertTimesCalled(5) } + Truth.assertThat(result.exceptionOrNull()).hasMessageThat().contains( + "Expected: 5, was: 1" + ) + } +} \ No newline at end of file diff --git a/formula-test/src/test/java/com/instacart/formula/test/TestFormulaObserverTest.kt b/formula-test/src/test/java/com/instacart/formula/test/TestFormulaObserverTest.kt new file mode 100644 index 000000000..9b5385f1b --- /dev/null +++ b/formula-test/src/test/java/com/instacart/formula/test/TestFormulaObserverTest.kt @@ -0,0 +1,58 @@ +package com.instacart.formula.test + +import com.google.common.truth.Truth +import com.instacart.formula.Evaluation +import com.instacart.formula.Snapshot +import com.instacart.formula.StatelessFormula +import org.junit.Test + +class TestFormulaObserverTest { + + @Test fun `assertOutput passes if count matches`() { + val formula = object : StatelessFormula() { + override fun Snapshot.evaluate(): Evaluation { + return Evaluation(input) + } + } + + val observer = formula.test() + observer.input(1) + observer.assertOutputCount(1) + + observer.input(10) + observer.assertOutputCount(2) + } + + @Test fun `assertOutput throws exception if count does not match`() { + val formula = object : StatelessFormula() { + override fun Snapshot.evaluate(): Evaluation { + return Evaluation(input) + } + } + + val observer = formula.test() + observer.input(1) + val result = kotlin.runCatching { + observer.assertOutputCount(5) + } + + Truth.assertThat(result.exceptionOrNull()).hasMessageThat().contains( + "Expected: 5, was: 1" + ) + } + + @Test fun `output throws error if formula is not running`() { + val formula = object : StatelessFormula() { + override fun Snapshot.evaluate(): Evaluation { + return Evaluation(input) + } + } + + val result = runCatching { + formula.test().output { } + } + Truth.assertThat(result.exceptionOrNull()).hasMessageThat().contains( + "Formula is not running. Call [TestFormulaObserver.input] to start it." + ) + } +} \ 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 7c4a511b1..b48469b06 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,6 +1,5 @@ package com.instacart.formula.test -import com.google.common.truth.Truth import com.google.common.truth.Truth.assertThat import junit.framework.Assert.fail import org.junit.Test @@ -9,8 +8,24 @@ class TestFormulaTest { @Test fun `assert running count is zero when formula is not running`() { val formula = TestSimpleFormula() formula.implementation.assertRunningCount(0) - formula.test().input(SimpleFormula.Input()) + + val observer = formula.test() + observer.input(SimpleFormula.Input()) formula.implementation.assertRunningCount(1) + + observer.dispose() + formula.implementation.assertRunningCount(0) + } + + @Test + fun `assert running count throws an exception when count does not match`() { + val formula = TestSimpleFormula() + val result = runCatching { + formula.implementation.assertRunningCount(5) + } + assertThat(result.exceptionOrNull()).hasMessageThat().contains( + "Expected 5 running formulas, but there were 0 instead" + ) } @Test fun `emits initial output when subscribed`() { diff --git a/formula-test/src/test/java/com/instacart/formula/test/TestListenerTest.kt b/formula-test/src/test/java/com/instacart/formula/test/TestListenerTest.kt new file mode 100644 index 000000000..6ba3bc5fb --- /dev/null +++ b/formula-test/src/test/java/com/instacart/formula/test/TestListenerTest.kt @@ -0,0 +1,34 @@ +package com.instacart.formula.test + +import com.google.common.truth.Truth +import org.junit.Test + +class TestListenerTest { + + @Test + fun `assertTimesCalled throws an exception when count does not match`() { + val listener = TestListener() + listener.assertTimesCalled(0) + listener.invoke("value") + listener.assertTimesCalled(1) + listener.invoke("second") + listener.assertTimesCalled(2) + + val result = runCatching { listener.assertTimesCalled(5) } + Truth.assertThat(result.exceptionOrNull()).hasMessageThat().contains( + "Expected: 5, was: 2" + ) + } + + @Test + fun values() { + val listener = TestListener() + listener.invoke("value") + listener.invoke("second") + listener.invoke("third") + + Truth.assertThat(listener.values()).containsExactly( + "value", "second", "third" + ).inOrder() + } +} \ No newline at end of file diff --git a/formula-test/src/test/java/com/instacart/formula/test/TestStreamTest.kt b/formula-test/src/test/java/com/instacart/formula/test/TestStreamTest.kt deleted file mode 100644 index f1bf66740..000000000 --- a/formula-test/src/test/java/com/instacart/formula/test/TestStreamTest.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.instacart.formula.test - -import com.google.common.truth.Truth.assertThat -import com.instacart.formula.Action -import com.instacart.formula.Cancelable -import org.junit.Test -import java.lang.IllegalStateException - -class TestStreamTest { - - @Test fun `assert values success`() { - multipleValueStream().test().assertValues(1, 2) - } - - @Test fun `assert value fails due to different size`() { - val exception = fails { multipleValueStream().test().assertValues(1) } - assertThat(exception).isInstanceOf(AssertionError::class.java) - } - - @Test fun `assert value fails due to different value`() { - val exception = fails { multipleValueStream().test().assertValues(1, 5) } - assertThat(exception).isInstanceOf(AssertionError::class.java) - } - - inline fun fails(action: () -> Unit): Throwable { - try { - action() - } catch (t: Error) { - return t - } - - throw IllegalStateException("Action succeeded.") - } - - fun multipleValueStream() = object : Action { - override fun start(send: (Int) -> Unit): Cancelable? { - send(1) - send(2) - return null - } - - override fun key(): Any = Unit - } -} diff --git a/samples/counter/src/test/java/com/instacart/formula/counter/CounterFormulaTest.kt b/samples/counter/src/test/java/com/instacart/formula/counter/CounterFormulaTest.kt index af2f1230e..3b54fbe46 100644 --- a/samples/counter/src/test/java/com/instacart/formula/counter/CounterFormulaTest.kt +++ b/samples/counter/src/test/java/com/instacart/formula/counter/CounterFormulaTest.kt @@ -9,7 +9,8 @@ class CounterFormulaTest { @Test fun `increment 5 times`() { CounterFormula() - .test(Unit) + .test() + .input(Unit) .output { onIncrement(Unit) } .output { onIncrement(Unit) } .output { onIncrement(Unit) } diff --git a/samples/custom-network-state-stream/src/test/java/com/instacart/formula/stopwatch/NetworkStateFormulaTest.kt b/samples/custom-network-state-stream/src/test/java/com/instacart/formula/stopwatch/NetworkStateFormulaTest.kt index c4a014eb9..9f4f02033 100644 --- a/samples/custom-network-state-stream/src/test/java/com/instacart/formula/stopwatch/NetworkStateFormulaTest.kt +++ b/samples/custom-network-state-stream/src/test/java/com/instacart/formula/stopwatch/NetworkStateFormulaTest.kt @@ -12,7 +12,8 @@ class NetworkStateFormulaTest { @Test fun offline() { formula(isOnline = false) - .test(Unit) + .test() + .input(Unit) .output { assertThat(status).isEqualTo("Network state: OFFLINE") } @@ -20,7 +21,8 @@ class NetworkStateFormulaTest { @Test fun connected() { formula(isOnline = true) - .test(Unit) + .test() + .input(Unit) .output { assertThat(status).isEqualTo("Network state: CONNECTED") } diff --git a/samples/stopwatch/src/test/java/com/instacart/formula/stopwatch/StopwatchFormulaTest.kt b/samples/stopwatch/src/test/java/com/instacart/formula/stopwatch/StopwatchFormulaTest.kt index e6c7e7ded..4d5cac294 100644 --- a/samples/stopwatch/src/test/java/com/instacart/formula/stopwatch/StopwatchFormulaTest.kt +++ b/samples/stopwatch/src/test/java/com/instacart/formula/stopwatch/StopwatchFormulaTest.kt @@ -7,6 +7,6 @@ class StopwatchFormulaTest { // TODO: @Test fun `increment 5 times`() { - StopwatchFormula().test(Unit) + StopwatchFormula().test().input(Unit) } } diff --git a/samples/todoapp/src/test/java/com/examples/todoapp/tasks/TaskListFormulaTest.kt b/samples/todoapp/src/test/java/com/examples/todoapp/tasks/TaskListFormulaTest.kt index f1fa8eec5..cfc72120e 100644 --- a/samples/todoapp/src/test/java/com/examples/todoapp/tasks/TaskListFormulaTest.kt +++ b/samples/todoapp/src/test/java/com/examples/todoapp/tasks/TaskListFormulaTest.kt @@ -18,7 +18,8 @@ class TaskListFormulaTest { val repo = TaskRepoFake() TaskListFormula(repo) - .test(TaskListFormula.Input(showToast = showToast)) + .test() + .input(TaskListFormula.Input(showToast = showToast)) .output { assertThat(items).hasSize(2) }