Skip to main content

Testing

Mojo includes a framework for developing and executing unit tests. The Mojo testing framework consists of a set of assertions defined as part of the Mojo standard library and the TestSuite struct for automatic test discovery and execution.

Get started

Let's start with a simple example of writing and running Mojo tests.

1. Write tests

For your first example of using the Mojo testing framework, create a file named test_quickstart.mojo containing the following code:

# Content of test_quickstart.mojo
from testing import assert_equal, TestSuite

def inc(n: Int) -> Int:
    return n + 1

def test_inc_zero():
    # This test contains an intentional logical error to show an example of
    # what a test failure looks like at runtime.
    assert_equal(inc(0), 0)

def test_inc_one():
    assert_equal(inc(1), 2)

def main():
    TestSuite.discover_tests[__functions_in_module()]().run()

In this file, the inc() function is the test target. The functions whose names begin with test_ are the tests. Usually you should define the target in a separate source file from its tests, but you can define them in the same file for this simple example.

A test function fails if it raises an error when executed, otherwise it passes. The two tests in this example use the assert_equal() function, which raises an error if the two values provided are not equal.

2. Execute tests

Then in the directory containing the file, execute the following command in your shell:

mojo run test_quickstart.mojo

You should see output similar to this (note that this example elides the full filesystem paths from the output shown):

Unhandled exception caught during execution:
Running 2 tests for ROOT_DIR/test_quickstart.mojo
    FAIL [ 0.009 ] test_inc_zero
      At ROOT_DIR/test_quickstart.mojo:40:5: AssertionError: `left == right` comparison failed:
         left: 1
        right: 0
    PASS [ 0.001 ] test_inc_one
--------
Summary [ 0.009 ] 2 tests run: 1 passed , 1 failed , 0 skipped
Test suite 'ROOT_DIR/test_quickstart.mojo' failed!

mojo: error: execution exited with a non-zero result: 1

The output shows each test as it runs with PASS or FAIL status and execution time, followed by a summary of tests run, passed, failed, and skipped. Failed tests display their error messages inline.

Next steps

  • Using Mojo assertion functions describes the assertion functions available to help implement tests.
  • Writing unit tests shows how to write unit tests and organize them into test files.
  • Our GitHub repo contains an example project to demonstrate unit testing. Several of the examples shown later are based on this project.

Using Mojo assertion functions

The Mojo standard library includes a testing module that defines several assertion functions for implementing tests. Each assertion returns None if its condition is met or raises an error if it isn't.

The boolean assertions report a basic error message when they fail.

from testing import *
assert_true(False)
Unhandled exception caught during execution

Error: At Expression [1] wrapper:14:16: AssertionError: condition was unexpectedly False

Each function also accepts an optional msg keyword argument for providing a custom message to include if the assertion fails.

assert_true(False, msg="paradoxes are not allowed")
Unhandled exception caught during execution

Error: At Expression [2] wrapper:14:16: AssertionError: paradoxes are not allowed

For comparing floating-point values, you should use assert_almost_equal(), which allows you to specify either an absolute or relative tolerance.

result = 10 / 3
assert_almost_equal(result, 3.33, atol=0.001, msg="close but no cigar")
Unhandled exception caught during execution

Error: At Expression [3] wrapper:15:24: AssertionError: 3.3333333333333335 is not close to 3.3300000000000001 with a diff of 0.0033333333333334103 (close but no cigar)

The testing module also defines a context manager, assert_raises(), to assert that a given code block correctly raises an expected error.

def inc(n: Int) -> Int:
    if n == Int.MAX:
         raise Error("inc overflow")
    return n + 1

print("Test passes because the error is raised")
with assert_raises():
    _ = inc(Int.MAX)

print("Test fails because the error isn't raised")
with assert_raises():
    _ = inc(Int.MIN)
Unhandled exception caught during execution

Test passes because the error is raised
Test fails because the error isn't raised
Error: AssertionError: Didn't raise at Expression [4] wrapper:18:23

You can also provide an optional contains argument to assert_raises() to indicate that the test passes only if the error message contains the substring specified. Other errors are propagated, failing the test.

print("Test passes because the error contains the substring")
with assert_raises(contains="required"):
    raise Error("missing required argument")

print("Test fails because the error doesn't contain the substring")
with assert_raises(contains="required"):
    raise Error("invalid value")
Unhandled exception caught during execution

Test passes because the error contains the substring
Test fails because the error doesn't contain the substring
Error: invalid value

Writing unit tests

A Mojo unit test is simply a function that fulfills all of these requirements:

  • Has a name that starts with test_ for automatic discovery.
  • Accepts no arguments.
  • Returns None.
  • Raises an error to indicate test failure.
  • Is defined at the module scope, not as a Mojo struct method.

You can use either def or fn to define a test function. Because a test function always raises an error to indicate failure, any test function defined using fn must include the raises declaration.

Generally, you should use the assertion utilities from the Mojo standard library testing module to implement your tests. You can include multiple related assertions in the same test function. However, if an assertion raises an error during execution, then the test function returns immediately, skipping any subsequent assertions.

Running tests with TestSuite

To run your tests, each test file must include a main() function that uses TestSuite.discover_tests() to automatically discover and execute all test functions in the module. The __functions_in_module() compiler intrinsic provides a list of all functions defined in the current module, which discover_tests() filters to find those with the test_ prefix.

Here is an example of a test file containing three tests for functions defined in a source module named my_target_module (which is not shown here).

# File: test_my_target_module.mojo

from my_target_module import convert_input, validate_input
from testing import assert_equal, assert_false, assert_raises, assert_true, TestSuite

def test_validate_input():
	assert_true(validate_input("good"), msg="'good' should be valid input")
	assert_false(validate_input("bad"), msg="'bad' should be invalid input")

def test_convert_input():
	assert_equal(convert_input("input1"), "output1")
	assert_equal(convert_input("input2"), "output2")

def test_convert_input_error():
	with assert_raises():
		_ = convert_input("garbage")

def main():
	TestSuite.discover_tests[__functions_in_module()]().run()

You can then use mojo run test_my_target_module.mojo to run the tests and report the results.

Was this page helpful?