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.