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
routePattern: string, // the route the request matched, as `b.router` sets
// during dispatch, and what a record names it by
routeLabel: string, // the route `b.router` resolved before dispatch, for
// a request that middleware handles or refuses
A fixture that sets neither route field stands for a request that
has not been routed, which `b.requestHelpers.resolveRoute` names
`"(unresolved)"` rather than by its URL.
}
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",
routePattern: "/users/:id",
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.requestHelpers.resolveRoute(req); // → "/users/:id"
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(opts?) #
{
highWaterMark: 0, // bytes outstanding before write() returns false; 0 disables
}
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).
Pass highWaterMark to exercise back-pressure. Without it write() always returns true, which is the one thing a real socket does not do — so a consumer that discards the return value and buffers without limit looks correct here and fails against a slow client. With it the double reports "full" once that many bytes are outstanding and emits drain on the next tick, which is the sequence a real socket produces.
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
var slow = b.testing.streamingRes({ highWaterMark: 4 });
slow.write("12345"); // → false, then emits "drain"
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 canceled 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 request opens its own connection and closes it after the response.
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