Skip to content

Testing

Tests live next to the code as test blocks:

Int add(Int a, Int b) { return a + b; }
test "add: identity" {
expect(add(0, 5)).toEqual(5);
expect(add(5, 0)).toEqual(5);
}
test "strings and lists" {
expect("resid").toHaveLength(5);
expect("resid").toMatch("^re[a-z]+d$");
expect([1, 2, 3]).toContain(3);
}

Run them with residc test:

$ residc test math_test.resid
math_test
✓ add: identity (0ms)
✓ strings and lists (0ms)
Failures: 0 | Passed: 2 | Duration: 0ms

--filter REGEX runs matching tests; --format pretty|tap|json chooses the report.

The exit status says what happened:

Code
0 every test passed
1 a test ran and failed
2 the file did not compile, or the invocation was malformed

So a file that does not build is never mistaken for a test that failed — worth knowing in CI, where the two want different responses.

residc test with no file runs every *_test.resid under a root, which is the current directory unless --root says otherwise:

$ residc test --root src
test: 3 test file/s under src
src/math_test.resid
math_test
✓ add: identity (0ms)
Failures: 0 | Passed: 1 | Duration: 0ms
src/net/http_test.resid
...
---
2 file/s passed, 1 failed, 0 did not compile

A test block is an entry point like main, so each test file is its own program: you see one summary per file, and the closing tally counts files. A failing file is reported and the rest still run. A file that does not compile is counted apart from a failing one, and the compile error wins the exit status. Per-file binaries go to target/resid unless --testbin-dir says otherwise. Build output (target), VCS metadata and node_modules are never searched.

To run a package’s tests with its dependencies resolved and their capability ceilings applied, use resid-manifest test. It resolves dependencies, writes the depmap, then hands discovery to the driver:

$ resid-manifest test resid.toml ./residc
test: 3 test file/s under /path/to/pkg/src
src/math_test.resid
...
---
3 file/s passed, 0 failed, 0 did not compile

A test file’s import "pkgname"; finds the same dependency the package’s own root does, inside the same ceiling. See resid-manifest.

For a suite assembled from data at run time — a table of cases generated by a loop, say — lib/testing.resid registers cases explicitly and runs them in one binary:

import "testing.resid";
Int add(Int a, Int b) { return a + b; }
Int main() {
List(TestCase) cases = [
test_case("add: identity", lambda() { expect(add(0, 5)).toEqual(5) }),
test_case("add: zero", lambda() { expect(add(0, 0)).toEqual(0) })
];
return run_tests("math", cases);
}

RESID_TEST_FORMAT and RESID_TEST_FILTER apply here too.

expect(value) wraps a value of any type, and the matcher is typed against it — so a mismatched matcher is a compile error, not a runtime one:

error[E0001]: type error: call toEqual expects (Int), got (Str)
--> math_test.resid:2:57
Matcher Checks
toEqual(v) / toBe(v) equal value
toNotEqual(v) different value
toBeTrue() / toBeFalse() a Bool
toBeNull() an Option is None
toContain(x) a list holds x
toHaveLength(n) a list or string length
toMatch(re) a string matches a regular expression
toBeCloseTo(x, tol) a Float within tol
toThrow() the given closure aborts
toSatisfy(pred) the predicate holds

Every matcher is an assertion returning nothing, so none of them can be assigned, interpolated, or negated — a test states what must hold, not what it holds.

toThrow takes its closure from a typed binding, since an inline lambda has no expected type to conform to:

Void closure() f = lambda() { expect(1).toEqual(2); };
expect(f).toThrow();

A test block is an entry point, so if the code under test needs a capability, the block declares it:

@requires(filesystem(readonly))
test "reads the config" {
expect(filesystem.exists("config.toml")).toBeTrue();
}

Authority enters a test exactly as it enters main, and the enclosing program’s or sandbox’s ceiling still bounds it.

Property-based testing (forall, generators, shrinking), snapshot testing, custom matchers, parallel test execution, per-test timeouts, and per-test result values. SPEC-testing.md is the full design document, with a section-by-section account of what exists.