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
beforeExitunlessBNL_TEST_AUTORUN=0env 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
beforeruns once,beforeEach/afterEachwrap each test, thenafterruns 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 = 1when autorun triggers. - Color reporter: prints ✓/✗ with diagnostics; diags are added via
t.diagnostic. - Keep tests small and deterministic since execution is serial.