Testing
Tova provides a built-in testing standard library with random data generators, property-based testing, snapshot testing, and spy/mock utilities. These functions are available in any test block.
For usage patterns and running tests, see the Test Runner guide.
Generators
The Gen object provides random data generators for property-based testing.
Gen.int
Gen.int() -> Generator<Int>
Gen.int(min, max) -> Generator<Int>Generates a random integer. Optionally constrained to a range.
Gen.int() // any integer
Gen.int(0, 100) // integer between 0 and 100
Gen.int(-10, 10) // integer between -10 and 10Gen.float
Gen.float() -> Generator<Float>Generates a random floating-point number.
Gen.float() // any floatGen.bool
Gen.bool() -> Generator<Bool>Generates a random boolean.
Gen.bool() // true or falseGen.string
Gen.string() -> Generator<String>
Gen.string(maxLen) -> Generator<String>Generates a random string. Optionally limited to a maximum length.
Gen.string() // random string
Gen.string(10) // random string up to 10 charactersGen.array
Gen.array(gen) -> Generator<[T]>
Gen.array(gen, maxLen) -> Generator<[T]>Generates a random array using another generator for elements. Optionally limited to a maximum length.
Gen.array(Gen.int()) // array of random integers
Gen.array(Gen.string(), 5) // array of up to 5 random stringsGen.oneOf
Gen.oneOf(choices) -> Generator<T>Generates a random value chosen from the given array.
Gen.oneOf(["red", "green", "blue"]) // random color
Gen.oneOf([1, 2, 3, 4, 5]) // random number from listProperty-Based Testing
forAll
forAll(generators, property, options?) -> NilRuns a property function with randomly generated inputs. The property should use assertions to verify invariants. By default, runs 100 iterations.
Parameters:
generators-- array ofGengeneratorsproperty-- function that receives generated values and asserts invariantsoptions-- optional object with{ runs: Int }to control iteration count
test "reverse is its own inverse" {
forAll([Gen.array(Gen.int())], fn(arr) {
assert_eq(reversed(reversed(arr)), arr)
})
}
test "sort produces sorted output" {
forAll([Gen.array(Gen.int())], fn(arr) {
result = sorted(arr)
assert(is_sorted(result))
})
}
test "addition is commutative" {
forAll([Gen.int(), Gen.int()], fn(a, b) {
assert_eq(a + b, b + a)
}, { runs: 500 })
}Snapshot Testing
assert_snapshot
assert_snapshot(value, name?) -> NilCompares a value against a previously stored snapshot. On first run, creates the snapshot file. On subsequent runs, asserts the value matches the stored snapshot.
Parameters:
value-- the value to snapshot (serialized to string)name-- optional name for the snapshot (defaults to auto-generated)
test "user serialization" {
user = User(1, "Alice", "alice@example.com")
assert_snapshot(user.to_json())
}
test "component rendering" {
html = render(Greeting("World"))
assert_snapshot(html, "greeting-html")
}To update snapshots when output intentionally changes, run:
tova test --update-snapshotsSpies
create_spy
create_spy(impl?) -> SpyCreates a spy function that records all calls. Optionally wraps an implementation function.
Parameters:
impl-- optional function to execute when the spy is called
Returns: A callable spy with tracking properties.
test "tracks calls" {
spy = create_spy()
spy("hello")
spy("world")
assert(spy.called)
assert_eq(spy.call_count, 2)
assert_eq(spy.calls, [["hello"], ["world"]])
}
test "spy with implementation" {
spy = create_spy(fn(x) x * 2)
result = spy(5)
assert_eq(result, 10)
assert_eq(spy.call_count, 1)
}Spy Properties
| Property | Type | Description |
|---|---|---|
.called | Bool | true if called at least once |
.call_count | Int | Number of times the spy was called |
.calls | [[T]] | Array of argument arrays from each call |
Spy Methods
| Method | Description |
|---|---|
.called_with(args) | Returns true if the spy was ever called with the given arguments |
.reset() | Clears all call tracking (resets called, call_count, calls) |
test "reset clears tracking" {
spy = create_spy()
spy(1)
spy(2)
assert_eq(spy.call_count, 2)
spy.reset()
assert_eq(spy.call_count, 0)
assert(not spy.called)
}Mocks
create_mock
create_mock(returnValue) -> MockCreates a mock function that returns a fixed value and tracks calls like a spy.
test "returns mock value" {
mock_fn = create_mock(42)
result = mock_fn("any", "args")
assert_eq(result, 42)
assert(mock_fn.called)
assert_eq(mock_fn.call_count, 1)
}
test "mock API response" {
mock_fetch = create_mock({ status: 200, body: '{"ok": true}' })
response = mock_fetch("/api/data")
assert_eq(response.status, 200)
}Mocks have the same tracking properties as spies: .called, .call_count, .calls, .called_with(), and .reset().