Skip to content

Assertions

Tova provides three assertion functions for verifying invariants during development and testing. Assertions throw errors when their conditions are not met, helping you catch bugs early.

assert

tova
assert(condition, msg?) -> Nil

Throws an error if condition is falsy (i.e., false or nil). If msg is provided, it is used as the error message.

tova
assert(true)                       // passes
assert(len(items) > 0)             // passes if items is non-empty
// assert(false)                   -- throws "Assertion failed"
// assert(false, "must be true")   -- throws "must be true"
tova
fn withdraw(account, amount) {
  assert(amount > 0, "withdrawal amount must be positive")
  assert(account.balance >= amount, "insufficient funds")
  { ...account, balance: account.balance - amount }
}

assert_eq

tova
assertEq(a, b, msg?) -> Nil

Throws an error if a and b are not strictly equal. The error message includes both values for easy debugging. An optional msg provides additional context.

Reference Equality

assertEq uses strict equality (===), which compares by reference for arrays and objects. Two arrays with the same contents are not equal: assertEq([1, 2], [1, 2]) will fail. Use assertEq(jsonStringify(a), jsonStringify(b)) to compare arrays or objects by value, or compare scalar properties individually.

tova
assertEq(2 + 2, 4)                        // passes
assertEq("hello", "hello")                 // passes
// assertEq(2 + 2, 5)                      -- throws, shows "4" vs "5"
// assertEq(x, 10, "x should be 10")       -- throws with custom message
tova
// In tests
fn test_add() {
  assertEq(add(1, 2), 3)
  assertEq(add(0, 0), 0)
  assertEq(add(-1, 1), 0)
}

fn test_string_utils() {
  assertEq(upper("hello"), "HELLO")
  assertEq(trim("  hi  "), "hi")
  assertEq(len(split("a,b,c", ",")), 3)
}

assert_ne

tova
assertNe(a, b, msg?) -> Nil

Throws an error if a === b. The inverse of assertEq.

tova
assertNe(1, 2)                              // passes
assertNe("hello", "world")                  // passes
// assertNe(5, 5)                            -- throws
// assertNe(x, 0, "x must not be zero")      -- throws with custom message
tova
fn test_random() {
  // random() should not return the same value twice (very unlikely)
  a = random()
  b = random()
  assertNe(a, b, "random() returned same value twice")
}

assert_throws

tova
assertThrows(func, expected?) -> Error

Calls func and asserts that it throws an error. If no error is thrown, the assertion fails. The optional expected parameter can be:

  • A string: passes if the error message contains the string
  • A RegExp: passes if the error message matches the pattern
tova
assertThrows(fn() divide(1, 0))                        // passes if it throws
assertThrows(fn() divide(1, 0), "divide by zero")      // passes if message contains "divide by zero"
assertThrows(fn() parse("abc"), re("invalid"))           // passes if message matches /invalid/
tova
fn test_validation() {
  assertThrows(fn() withdraw(account, -10), "must be positive")
  assertThrows(fn() withdraw(account, 99999), "insufficient")
}

assert_snapshot

tova
assertSnapshot(value, name?) -> Nil

Compares a value against a previously stored snapshot. On first run, creates the snapshot. On subsequent runs, asserts the value matches the stored snapshot. The optional name parameter provides a label for the snapshot file.

tova
test "user serialization" {
  user = User(1, "Alice", "alice@example.com")
  assertSnapshot(user.to_json())
}

test "rendering output" {
  html = render(Greeting("World"))
  assertSnapshot(html, "greeting-output")
}

To update snapshots when output intentionally changes:

bash
tova test --update-snapshots

Usage in Tests

Assertions are the primary tool for writing Tova tests. Tova test files use fn test_*() naming conventions:

tova
fn test_sorted() {
  assertEq(jsonStringify(sorted([3, 1, 2])), jsonStringify([1, 2, 3]))
  assertEq(len(sorted([])), 0)
  assertEq(first(sorted([1])), 1)
}

fn test_reversed() {
  assertEq(jsonStringify(reversed([1, 2, 3])), jsonStringify([3, 2, 1]))
  assertEq(len(reversed([])), 0)
}

fn test_partition() {
  evens, odds = partition([1, 2, 3, 4], fn(x) x % 2 == 0)
  assertEq(jsonStringify(evens), jsonStringify([2, 4]))
  assertEq(jsonStringify(odds), jsonStringify([1, 3]))
}

Usage for Preconditions

Assertions are useful for validating function inputs during development:

tova
fn divide(a, b) {
  assert(b != 0, "cannot divide by zero")
  a / b
}

fn get_page(items, page_size, page_num) {
  assert(page_size > 0, "page_size must be positive")
  assert(page_num >= 0, "page_num must be non-negative")
  items |> drop(page_size * page_num) |> take(page_size)
}

Usage for Debugging

When debugging, assertions help narrow down where things go wrong:

tova
fn process_data(raw) {
  parsed = parse(raw)
  assert(parsed != nil, "parse returned nil for: {raw}")

  transformed = transform(parsed)
  assert(len(transformed) > 0, "transform produced empty result")

  result = validate(transformed)
  assert(result.isOk(), "validation failed: {result.unwrapErr()}")

  result.unwrap()
}

Comparison with Result/Option

Assertions and Result/Option serve different purposes:

ApproachUse When
assertCatching programmer errors; conditions that should never be false if the code is correct
Result / OptionExpected failure cases; user input validation; I/O operations that can fail
tova
// assert: a bug if this fails -- should never happen
assert(len(matrix) > 0, "matrix must not be empty")

// Result: expected failure -- user might give bad input
fn parse_age(input) {
  n = parse_int(input)
  if n.isErr() { return Err("not a number") }
  age = n.unwrap()
  if age < 0 or age > 150 { return Err("age out of range") }
  Ok(age)
}

Released under the MIT License.