Getting Started

Spawn has built-in support for tests. All tests in Spawn are written inside files with a special suffix _test. For example, tests for the foo.sp file are usually written in the foo_test.sp file.

Each file defines a set of tests that will be run with spawn --test. To define a test, use the test keyword followed by the test name, enclosed in a string literal:

test 'push to full array' { mut arr := [1, 2, 3] arr.push(4) t.assert_eq(arr, [1, 2, 3, 4], 'actual array should be equal to expected') }

By using string literals as test names, you can use spaces and special characters in test names and write test names in a more natural style.

Currently, each test file must start with a module definition main and import all necessary modules for testing explicitly, even if the test is located in the directory with the module under test.

module main import foo test 'can parse with foo' { foo.parse('1.').unwrap() }

At the moment, inside tests, it is possible to access private members of the module that are being tested.

Each test defines a set of assertions that must be met to pass the test. In the example above, the test checks that the array arr after calling the push method is equal to the expected value [1, 2, 3, 4]. If the condition is not met, the test fails.

A special variable t with &mut testing.Tester type is defined in each test and provides a set of assertions. The following assertions are available:

  • assert_eq(a, b, msg): checks that a is equal to b
  • assert_ne(a, b, msg): checks that a is not equal to b
  • assert_opt_eq(a, b, msg): checks that the value inside a with type ?T is equal to b, fails if a is none
  • assert_less(a, b, msg): checks that a is less than b
  • assert_greater(a, b, msg): checks that a is greater than b
  • assert_le(a, b, msg): checks that a is less than or equal to b
  • assert_ge(a, b, msg): checks that a is greater than or equal to b
  • assert_true(a, msg): checks that a is true
  • assert_false(a, msg): checks that a is false
  • assert_none(a, msg): checks that a is equal to none
  • assert_not_none(a, msg): checks that a is not equal to none

The test can also be failed using the fail(msg) method:

test 'failing test' { t.fail('this test will fail') }

Tests attributes

Each test can have attributes to change the behavior. For example, the #[skip] attribute allows you to disable a test.

The following attributes are available:

#[skip]

A test marked with the #[skip] attribute will be skipped when running tests:

#[skip] test 'this test will be skipped' { t.fail('no failure here') }

#[must_panic]

A test marked with the #[must_panic] attribute will only be considered successful if a panic occurs within it. Otherwise, it will fail:

#[must_panic] test 'this test must panic' { panic('this test will pass') }

#[retry(N)]

A test marked with the #[retry(N)] attribute will be rerun N times if it fails. If the test fails after N attempts, it will fail:

module main import rand #[retry(19)] test 'this test will be retried 10 times' { t.assert_eq(rand.next_u32() % 5, 4) }

#[run_if(condition)]

A test marked with the #[run_if(condition)] attribute will only be run if condition is true. The condition can be any compile time condition.

For example, you can define your compile time constant and use it as a condition:

comptime const is_ci = false #[run_if(is_ci)] test 'this test will be run only on CI' { t.assert_eq(1, 2, '1 should be equal to 2' }

Now, when running a test normally via spawn --test, the test will be skipped, since is_ci is false by default. However, if you run the test with -d is_ci=true, the test will run.

This attribute can also be used to run tests on a specific OS or architecture:

#[run_if(windows)] test 'this test will be run only on Windows' { t.assert_eq(1, 2, '1 should be equal to 2' } #[run_if(darwin && amd64)] test 'this test will be run only on macOS with amd64 architecture' { t.assert_eq(1, 2, '1 should be equal to 2' }
On this page