Testing
Operator-facing test helpers. Every helper threads through an existing framework primitive rather than rolling its own timer races or polling loops, so test code exercises the same code paths production traffic does.
The surface covers four concerns: HTTP request/response fixture builders (mockReq / mockRes / bodyReq / bodyRes / streamingRes), controllable time / network / fs fakes (fakeClock / fakeHttpClient / tempDir / listenOnRandomPort / makeFakeOtelApi), capturing taps for the framework's emit surfaces (captureAudit / captureObservability / captureMetricsTap), and async test helpers including a supertest-style chainable HTTP runner (runMiddleware / waitFor / request).
Primitive-mapping for the threaded-through helpers:
- waitFor poll loop → b.safeAsync.sleep
- waitFor / runMiddleware overall cap → b.safeAsync.withTimeout
- mockReq actor shape → compatible with b.requestHelpers.extractActorContext
- captureObservability → matches b.observability.tap + .event contracts
- captureAudit → matches b.audit.safeEmit (drop-silent)
- fakeHttpClient → matches b.httpClient.request response shape
- tempDir path safety → mirrors lib/static.js _resolveSafe containment check
- TestingError → b.frameworkError.defineClass(...{ alwaysPermanent: true })
What is intentionally NOT here: assertion library / test runner, DB transaction-rollback wrapper (b.db.transaction already exists), snapshot or property-based testing helpers (operator brings their own), and framework-internal fixtures that boot b.db with vault (those stay in test/helpers/db.js).
b.testing.mockReq(opts) #
{
method: string, // HTTP method; defaults to "GET"
url: string, // request-target; defaults to "/"
pathname: string, // optional override; defaults to URL pre-`?`
headers: object, // header map (lower-cased on read)
userAgent: string, // shorthand for headers["user-agent"]
requestId: string, // shorthand for headers["x-request-id"]
ip: string, // socket/connection.remoteAddress
socket: object, // explicit socket override
connection: object, // explicit connection override
}
Build a plain-object request fixture that satisfies every field b.requestHelpers.extractActorContext reads (headers, socket, connection, method, url). Sensible defaults for every field so passing {} produces a complete, self-consistent request.
var req = b.testing.mockReq({
method: "POST",
url: "/users/42",
userAgent: "test-agent/1.0",
ip: "10.0.0.1",
});
req.method; // → "POST"
req.headers["user-agent"]; // → "test-agent/1.0"
req.socket.remoteAddress; // → "10.0.0.1"
b.testing.mockRes() #
Build a buffered response fixture that captures setHeader, writeHead, and end calls. The hidden _captured() accessor returns { status, headers, body, ended } for assertions. Use this when the middleware under test writes a single response body — for streaming responses, use streamingRes() instead.
var res = b.testing.mockRes();
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("hello");
var captured = res._captured();
captured.status; // → 200
captured.headers["content-type"]; // → "text/plain"
captured.body; // → "hello"
captured.ended; // → true
b.testing.bodyReq(method, headers, body) #
Build an EventEmitter-backed request that emits a single data chunk and an end event on the next tick. Use this for body-parser / form / file-upload middleware tests where the consumer reads via req.on("data", ...) and req.on("end", ...).
var req = b.testing.bodyReq("POST", { "content-type": "application/json" }, '{"a":1}');
var chunks = [];
req.on("data", function (c) { chunks.push(c); });
req.on("end", function () {
var body = Buffer.concat(chunks).toString("utf8");
body; // → '{"a":1}'
});
b.testing.bodyRes() #
Build an EventEmitter-backed response that captures the end() payload onto res._captured (string-concatenated) and emits a finish event when ended. Use this paired with bodyReq for body- parser middleware tests that need to await the finish lifecycle.
var res = b.testing.bodyRes();
res.on("finish", function () {
res.statusCode; // → 201
res._captured; // → "ok"
});
res.writeHead(201, { "content-type": "text/plain" });
res.end("ok");
b.testing.streamingRes() #
Build an EventEmitter-backed response that buffers every write() chunk into res._chunks and exposes res._captured() as a single Buffer of the full payload. Use this for middleware that streams via repeated res.write(chunk) calls (gzip, server-sent events, NDJSON producers, range responses).
var res = b.testing.streamingRes();
res.writeHead(200, { "content-type": "application/octet-stream" });
res.write(Buffer.from("hel"));
res.write("lo");
res.end();
res._captured().toString("utf8"); // → "hello"
res._statusCode; // → 200
b.testing.fakeClock(initialMs) #
Build a controllable clock whose .now method is suitable as the clock opt on every framework primitive that takes one (b.cache.create, b.apiKey.*, b.permissions, b.scheduler, b.seeders, b.session.*, …). initialMs defaults to 1_000_000. advance(ms) jumps forward, set(ms) jumps to an absolute instant, and the ms getter reads the current value without invoking .now.
var clk = b.testing.fakeClock(1700000000000);
clk.now(); // → 1700000000000
clk.advance(60000);
clk.now(); // → 1700000060000
clk.set(1800000000000);
clk.ms; // → 1800000000000
b.testing.fakeHttpClient(responder) #
Drop-in stand-in for b.httpClient. The responder(req) callback receives the request object every call site passes to .request and returns the canned response (sync or via a Promise). Every outbound request is recorded on .calls for later assertion.
var hc = b.testing.fakeHttpClient(function (req) {
return { statusCode: 200, body: Buffer.from('{"ok":true}') };
});
var res = await hc.request({ method: "GET", url: "https://api.example.com/health" });
res.statusCode; // → 200
hc.calls.length; // → 1
hc.calls[0].url; // → "https://api.example.com/health"
b.testing.captureAudit() #
Build a capturing stand-in for b.audit that satisfies the safeEmit(event) contract every framework primitive uses. Pass the returned object as the audit opt; events flow into .captured, .clear() empties the buffer, and .byAction(name) filters by the action field (the convention every framework emit uses).
var audit = b.testing.captureAudit();
audit.safeEmit({ action: "notify.send.success", actor: "alice" });
audit.safeEmit({ action: "notify.send.failure", actor: "alice" });
audit.captured.length; // → 2
audit.byAction("notify.send.success").length; // → 1
audit.clear();
audit.captured.length; // → 0
b.testing.captureObservability() #
Build a capturing stand-in for b.observability. The returned object exposes event(name, value, labels) and tap(name, attrs, fn) matching the framework's contracts; each call appends an entry to .captured ({ kind: "event" | "tap" | "tap.end" | "tap.error", … }). tap runs fn(null) (no tracer active under capture) and forwards both sync return values and promise resolutions/rejections.
var obs = b.testing.captureObservability();
obs.event("cache.hit", 1, { ns: "users" });
var ret = obs.tap("widgets.load", { id: 42 }, function () { return "ok"; });
ret; // → "ok"
obs.captured.length; // → 3
obs.byName("cache.hit").length; // → 1
b.testing.captureMetricsTap() #
Swap b.metrics.tap with a capturing function and return a handle with .captured, .byName(name), .clear(), and crucially .restore(). The operator MUST call .restore() in a finally to revert — failure to restore leaks state across tests.
var taps = b.testing.captureMetricsTap();
try {
b.metrics.tap("widgets.created", 1, { kind: "alpha" });
b.metrics.tap("widgets.created", 1, { kind: "beta" });
taps.captured.length; // → 2
taps.byName("widgets.created").length; // → 2
} finally {
taps.restore();
}
b.testing.runMiddleware(middleware, req, res, opts) #
{
timeoutMs: number, // overall cap; defaults to 5000 (0 disables)
}
Drive a 3-arg (req, res, next) middleware to either next() OR res.end() completion and return { nextCalled, nextError, req, res, ended }. Sync throws and rejected-promise returns are mapped onto nextError. b.safeAsync.withTimeout caps the wait so a middleware that never settles fails the test fast instead of hanging. req / res default to fresh mockReq() / mockRes().
var auth = function (req, res, next) {
if (!req.headers.authorization) {
res.writeHead(401);
res.end("unauthorized");
return;
}
next();
};
var captured = await b.testing.runMiddleware(
auth,
b.testing.mockReq({ url: "/secret" }),
b.testing.mockRes(),
{ timeoutMs: 1000 }
);
captured.nextCalled; // → false
captured.ended; // → true
captured.res._captured().status; // → 401
b.testing.waitFor(predicate, opts) #
{
timeoutMs: number, // overall cap; defaults to 1000
intervalMs: number, // poll interval; defaults to 10
signal: AbortSignal, // cooperative cancellation
}
Poll predicate() (sync or async) until it returns truthy or timeoutMs elapses. The poll loop uses b.safeAsync.sleep (NOT raw setTimeout) so timer cleanup is uniform with the rest of the framework, and b.safeAsync.withTimeout enforces the overall cap. Pass opts.signal to abort early (a cancelled AbortController resolves the loop without throwing).
var jobs = [];
setTimeout(function () { jobs.push({ id: 1 }); }, 30);
var result = await b.testing.waitFor(
function () { return jobs.length > 0 ? jobs[0] : false; },
{ timeoutMs: 500, intervalMs: 5 }
);
result.id; // → 1
b.testing.tempDir(prefix) #
Create an os.tmpdir()-rooted directory and return { path, cleanup }. prefix must be an identifier-like string (no .., /, \, or null bytes); a containment check mirroring lib/static.js _resolveSafe verifies the resolved path stays inside os.tmpdir() before any file is written. cleanup() is idempotent and best-effort on Windows-locked files.
var dir = b.testing.tempDir("my-fixture");
try {
var fs = require("node:fs");
var path = require("node:path");
fs.writeFileSync(path.join(dir.path, "fixture.json"), '{"ok":1}');
dir.path.indexOf("my-fixture-") !== -1; // → true
} finally {
dir.cleanup();
}
b.testing.listenOnRandomPort(server, host) #
Bind a Node http.Server to an OS-assigned ephemeral port on the loopback interface (host defaults to "127.0.0.1") and resolve with the chosen port number. Use this when a test needs a real listening server but doesn't want to hard-code a port. For supertest-style routing, prefer b.testing.request which manages the listen/close lifecycle automatically.
var nodeHttp = require("node:http");
var server = nodeHttp.createServer(function (req, res) {
res.writeHead(200);
res.end("ok");
});
var port = await b.testing.listenOnRandomPort(server);
typeof port === "number" && port > 0; // → true
server.close();
b.testing.request(target) #
Supertest-style chainable HTTP test runner. target may be a b.router instance (uses .handle(req, res)), a (req, res) => void listener function, or an existing http.Server / https.Server. The runner spins up a real ephemeral-port http.Server so the request flows through the full Node HTTP stack — the same code path production traffic takes — and tears it down when the awaited chain resolves or rejects.
Each verb (get / post / put / patch / delete / head / options) returns a chain with .set(k, v) / .set(obj), .send(body) (Buffer / string / JSON-serialized object), and .expect(statusOrFn). Awaiting the chain resolves to { status, headers, body, text, json }.
var listener = function (req, res) {
res.writeHead(200, { "content-type": "application/json" });
res.end('{"ok":true}');
};
var res = await b.testing.request(listener)
.get("/health")
.set("X-Request-Id", "abc")
.expect(200);
res.status; // → 200
res.json.ok; // → true
b.testing.makeFakeOtelApi() #
Build a minimal fake of @opentelemetry/api covering exactly the subset b.tracing consumes (trace.getTracer, trace.setSpan, trace.getActiveSpan, context.active, context.with, plus a SpanKind enum). Each started span records attributes, events, exceptions, status, and end into _spans for assertion. Operators inject this where b.tracing would normally be wired to the real OTel API.
var fake = b.testing.makeFakeOtelApi();
var tracer = fake.trace.getTracer("test");
var span = tracer.startSpan("widgets.load", { attributes: { id: 42 } });
span.setAttribute("status", "ok");
span.end();
fake._spans.length; // → 1
fake._spans[0]._attrs.id; // → 42
fake._spans[0]._ended; // → true
Last updated 2026-08-08T16:39:49.652Z by seeder.