bnl_test
The bnl_test
module is a tiny test framework with a familiar API. It supports suites (describe
), tests (test
/it
), hooks (before/after/beforeEach/afterEach
), timeouts, skip/todo/only, and a programmatic run()
. It also auto‑runs collected tests on process drain unless disabled.
API Surface
Defining
describe(name, [options], fn)
— define a suite; can nest.test(name, [options], fn)
/it(...)
— define a test.- Variants:
test.skip(name, fn)
,test.todo(name)
,test.only(name, [options], fn)
.
Hooks
before(fn)
,after(fn)
— run once per suite.beforeEach(fn)
,afterEach(fn)
— run around each test in suite nesting order.
Running
run([options])
→{ pass, fail, skip, todo, duration }
.- Auto‑run: tests run automatically on
beforeExit
unlessBNL_TEST_AUTORUN=0
env is set.
Test options
timeout
(ms; default 2000) — per‑test timeout.only
(boolean) — focus this test.skip
,todo
(boolean).done
(boolean) — allow callback‑style completion (alternative to returning a Promise).
Test context t
t.diagnostic(msg)
— add a diagnostic line to the report.t.cleanup(fn)
— register cleanup (runs LIFO even on failures).t.setTimeout(ms)
— change current test timeout at runtime.t.test(name, [options], fn)
— schedule a subtest under the same parent suite.t.skip()
— skip the current test.
Behavior & Semantics
- Only mode: if any test/suite is marked
only
, the runner executes only those tests (and their parents). - Skip vs Todo: skipped tests are reported as skipped; todo are placeholders (no function).
- Timeouts: exceeding a test’s timeout fails it with
TestTimeoutError
. You can adjust per test using options ort.setTimeout(ms)
. - Execution order: depth‑first, serial; suite
before
runs once,beforeEach
/afterEach
wrap each test, thenafter
runs once. - Errors: any thrown error (or rejected promise) fails the test; cleanups still run.
Examples (English only)
const { test, it, describe, before, after, beforeEach, afterEach, run } = require("bnl_test");
describe("math", () => {
let acc = 0;
before(() => { acc = 0; });
beforeEach(() => { acc += 1; });
afterEach(() => { acc = 0; });
it("adds", async (t) => {
t.diagnostic("starting");
const sum = 1 + 2;
if (sum !== 3) throw new Error("bad math");
t.cleanup(() => console.log("cleanup called"));
});
test.skip("multiplies", () => {});
test.todo("divides later");
});
// Focused test (only)
test.only("focused", async () => {
await new Promise(r => setTimeout(r, 10));
});
// Programmatic run (optional; auto-run happens by default)
(async () => {
const summary = await run();
console.log("summary:", summary);
})();
// Done-style callback test (avoids returning a Promise)
test("with done", { done: true }, (t, done) => {
setTimeout(() => done(), 5);
});
// Subtests from within a test
test("parent", async (t) => {
t.test("child 1", () => {});
t.test("child 2", { timeout: 100 }, async () => {});
});
Notes
- Autorun can be disabled by setting environment variable
BNL_TEST_AUTORUN=0
. - Exit code: failures set
process.exitCode = 1
when autorun triggers. - Color reporter: prints ✓/✗ with diagnostics; diags are added via
t.diagnostic
. - Keep tests small and deterministic since execution is serial.