IMPORTANT: To view this page as Markdown, append `.md` to the URL (e.g. /docs/manual/basics.md). For the complete Mojo documentation index, see llms.txt.
Skip to main content
Version: Nightly
For the complete Mojo documentation index, see llms.txt. Markdown versions of all pages are available by appending .md to any URL (e.g. /docs/manual/basics.md).

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 std.testing import assert_equal, TestSuite

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

def test_inc_zero() raises:
# 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() raises:
assert_equal(inc(1), 2)

def main() raises:
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 std.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) raises -> 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.

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 std.testing import assert_equal, assert_false, assert_raises, assert_true, TestSuite

def test_validate_input() raises:
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() raises:
assert_equal(convert_input("input1"), "output1")
assert_equal(convert_input("input2"), "output2")

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

def main() raises:
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.

Filtering tests

By default, a TestSuite runs every test discovered in a test file. You can filter which tests run, either from the command line or programmatically. This is useful when you want to focus on a single failing test, exclude a known-broken or flaky test, or list the tests in a file without running them.

The examples in this section use the test files from the example project, which defines the tests test_inc_valid() and test_inc_max() in test/my_math/test_inc.mojo.

Filter tests from the command line

A test file that uses TestSuite accepts the following command line flags:

  • --skip <names>: Run all tests except the named ones (a skip-list).
  • --only <names>: Run only the named tests (an allow-list).
  • --skip-all: Skip every test, collecting and listing the tests without running any of them.

For example, to run every test in test_inc.mojo except test_inc_max():

mojo run -I src test/my_math/test_inc.mojo --skip test_inc_max

Skipped tests appear in the output with SKIP status:

Running 2 tests for test/my_math/test_inc.mojo
PASS [ 0.001 ] test_inc_valid
SKIP [ 0.001 ] test_inc_max
--------
Summary [ 0.001 ] 2 tests run: 1 passed , 0 failed , 1 skipped

To run only test_inc_valid(), use the --only flag:

mojo run -I src test/my_math/test_inc.mojo --only test_inc_valid

To collect and list the tests without running any of them, use --skip-all. This is handy when you want to see which tests a file contains:

mojo run -I src test/my_math/test_inc.mojo --skip-all

A few things to note about these flags:

  • The --skip and --only flags each accept multiple test names as separate, space-separated arguments. For example, --skip test_inc_valid test_inc_max skips both tests. Don't combine the names into a single quoted argument.
  • Test names must match exactly. If you pass a name that doesn't correspond to a discovered test, the suite raises an error and exits with a non-zero status.
  • You can use only one of these flags per command. The --skip-all flag takes no test names.

Skip tests programmatically

Instead of, or in addition to, filtering from the command line, you can skip specific tests in the test file itself. Capture the suite returned by discover_tests() in a variable and call skip() before running the suite:

def main() raises:
var suite = TestSuite.discover_tests[__functions_in_module()]()
suite.skip[test_inc_max]()
suite^.run()

Note the ^ transfer sigil in the call to run(): the method consumes the suite, so you must transfer ownership of the suite to it.

To skip more than one test, call skip() once for each test you want to skip:

def main() raises:
var suite = TestSuite.discover_tests[__functions_in_module()]()
suite.skip[test_broken]()
suite.skip[test_flaky]()
suite^.run()

A programmatic skip always takes effect, even when you use --only to allow a test that's also skipped in the code. This makes skip() a good fit for tests that you want to keep disabled regardless of the command line filters, such as a test that's broken, flaky, or that depends on an unavailable environment.

For more information, see the TestSuite API reference.