Discovering the Bnlang Test Framework
Bnlang ships with a tiny test framework in the standard library — test. It gives you test(name, fn) to register tests, a handful of assertion helpers, and run() to execute everything and exit non-zero on failure.
There's no separate test discovery binary. A test file is just a regular Bnlang script that imports test, registers cases, and calls t.run().
A Minimal Test File
// file: tests/math_spec.bnl
import "test" as t;
t.test("addition", function () {
t.equal(2 + 2, 4);
});
t.test("upper case", function () {
t.equal("foo".to_upper(), "FOO");
});
t.run();
Available Assertions
The framework keeps its surface tiny. Every assertion throws on failure; run() catches and reports.
t.equal(actual, expected)— value compare.t.not_equal(actual, expected)— opposite ofequal.t.is_true(v)/t.is_false(v)— boolean checks.t.is_null(v)/t.not_null(v)— null checks.t.contains(haystack, needle)— substring (for strings) or membership (for lists).t.throws(fn)— assert thatfn()throws.t.throws_with(fn, substring)— likethrows, but also checks the message.
Running a Test File
Just run the file with bnl. t.run() prints a summary to stdout and exits the process with code 1 on any failure, so CI picks it up automatically.
bnl tests/math_spec.bnl
Best Practices
- Keep each
test()focused on a single behavior — the name should describe the assertion. - Group related test files under a
tests/directory. There's no magic discovery, but it makes shell globs (bnl tests/*.bnl) straightforward. - Use
t.throws_withto assert on error messages — it catches regressions where the right exception fires for the wrong reason.