Mojo Testing

Testing verifies that your code produces correct results across a range of inputs — before you deploy it or share it with others. Well-tested code catches regressions (bugs introduced by later changes) immediately and gives you confidence to refactor aggressively. Mojo's testing module provides assertion functions that form the building blocks of a complete test suite.

The Safety Net Analogy

  Code without tests:                Code with tests:
  ┌─────────────────┐                ┌─────────────────┐
  │  You change     │                │  You change     │
  │  something      │                │  something      │
  └────────┬────────┘                └────────┬────────┘
           │                                  │
           ▼                                  ▼
  ┌─────────────────┐                ┌─────────────────┐
  │  Hope nothing   │                │  Run tests      │
  │  broke          │                │  in seconds     │
  │  (find out in   │                └────────┬────────┘
  │   production)   │                         │
  └─────────────────┘                ┌────────┴────────┐
                                     │                 │
                                    Pass              Fail
                                  (safe to            (fix now,
                                   ship)              not later)

The Testing Module

from testing import (
    assert_true,
    assert_false,
    assert_equal,
    assert_not_equal,
    assert_almost_equal,
)

Writing Your First Test

from testing import assert_equal, assert_true

# The function being tested
fn celsius_to_fahrenheit(c: Float64) -> Float64:
    return c * 9.0 / 5.0 + 32.0

# The test function
fn test_celsius_to_fahrenheit() raises:
    assert_equal(celsius_to_fahrenheit(0.0),   32.0,  "0°C = 32°F")
    assert_equal(celsius_to_fahrenheit(100.0), 212.0, "100°C = 212°F")
    assert_equal(celsius_to_fahrenheit(-40.0), -40.0, "-40°C = -40°F")
    assert_true(celsius_to_fahrenheit(37.0) > 98.0, "body temp above 98°F")
    print("test_celsius_to_fahrenheit passed")

fn main() raises:
    test_celsius_to_fahrenheit()

Testing Floating-Point Results

Never use assert_equal for floating-point results — tiny rounding differences cause false failures. Use assert_almost_equal with a tolerance instead.

from testing import assert_almost_equal
from math import sqrt

fn test_sqrt() raises:
    assert_almost_equal(sqrt(2.0), 1.41421356, atol=1e-6, msg="sqrt(2)")
    assert_almost_equal(sqrt(9.0), 3.0,         atol=1e-9, msg="sqrt(9)")
    print("test_sqrt passed")
Why atol matters:
  sqrt(2.0) computed = 1.4142135623730951
  expected           = 1.41421356
  difference         = 0.0000000023...   < 1e-6 → PASS

  assert_equal would fail because the floats are not bit-for-bit identical.
  assert_almost_equal passes because the difference is within tolerance.

Organizing Tests into Test Functions

Each test function covers one unit of behavior. Name them clearly so failures identify the problem immediately.

from testing import assert_equal, assert_true, assert_false

fn is_prime(n: Int) -> Bool:
    if n < 2: return False
    var i = 2
    while i * i <= n:
        if n % i == 0: return False
        i += 1
    return True

fn test_is_prime_small_primes() raises:
    assert_true(is_prime(2),   "2 is prime")
    assert_true(is_prime(3),   "3 is prime")
    assert_true(is_prime(5),   "5 is prime")
    assert_true(is_prime(7),   "7 is prime")
    assert_true(is_prime(11),  "11 is prime")
    print("test_is_prime_small_primes passed")

fn test_is_prime_composites() raises:
    assert_false(is_prime(1),  "1 is not prime")
    assert_false(is_prime(4),  "4 is not prime")
    assert_false(is_prime(9),  "9 is not prime")
    assert_false(is_prime(15), "15 is not prime")
    print("test_is_prime_composites passed")

fn test_is_prime_edge_cases() raises:
    assert_false(is_prime(0),  "0 is not prime")
    assert_false(is_prime(-5), "negative is not prime")
    print("test_is_prime_edge_cases passed")

fn main() raises:
    test_is_prime_small_primes()
    test_is_prime_composites()
    test_is_prime_edge_cases()
    print("All tests passed!")

Testing Error Conditions

from testing import assert_true

fn divide(a: Float64, b: Float64) raises -> Float64:
    if b == 0.0:
        raise Error("division by zero")
    return a / b

fn test_divide_by_zero_raises() raises:
    var error_raised = False
    try:
        _ = divide(10.0, 0.0)
    except e:
        error_raised = True
        assert_true(str(e).find("division by zero") != -1,
                    "error message must mention 'division by zero'")

    assert_true(error_raised, "divide(10, 0) must raise an error")
    print("test_divide_by_zero_raises passed")

Test Coverage: Normal, Edge, Error Cases

For any function, test three categories:

Normal cases:
  → Typical valid inputs the function is designed for
  → The "happy path" that most callers use

Edge cases:
  → Boundary values: 0, -1, max_int, empty string, single-element list
  → Off-by-one scenarios: exactly at a limit, one above, one below

Error cases:
  → Invalid inputs that should raise errors
  → Verify the error message is descriptive
  → Confirm the function does NOT succeed on bad input
fn test_list_sum() raises:
    # Normal
    var a = List[Int](1, 2, 3, 4, 5)
    assert_equal(list_sum(a), 15, "sum of 1..5")

    # Edge: single element
    var b = List[Int](42)
    assert_equal(list_sum(b), 42, "single element sum")

    # Edge: all zeros
    var c = List[Int](0, 0, 0)
    assert_equal(list_sum(c), 0, "all zeros sum")

    # Edge: negative numbers
    var d = List[Int](-1, -2, -3)
    assert_equal(list_sum(d), -6, "negative sum")

Running Tests Selectively

fn run_all_tests() raises:
    test_celsius_to_fahrenheit()
    test_is_prime_small_primes()
    test_is_prime_composites()
    test_is_prime_edge_cases()
    test_divide_by_zero_raises()
    print("=== All tests passed ===")

fn main() raises:
    run_all_tests()

Key Takeaways

Tests are functions that call assertions on your production code. Use assert_equal for exact comparisons and assert_almost_equal with a tolerance for floating-point results. Name test functions clearly so failures pinpoint the problem. Cover three categories for every function: normal inputs, edge cases, and error conditions. Collect all test calls in a run_all_tests function. Run tests after every code change to catch regressions instantly. Tests are not overhead — they are the fastest way to know your code is correct.

Leave a Comment

Your email address will not be published. Required fields are marked *