-
-
Notifications
You must be signed in to change notification settings - Fork 0
server
HTTP server harness for tests: spin up a real node:http server on an ephemeral port, run your client code against it, and tear it down reliably — plus a request recorder for asserting what your code sent over the wire. Works on Node, Bun, and Deno.
Not to be confused with tape6-server, the CLI web server that hosts browser-based test runs. This page is about the
tape-six/server.jsmodule you import inside test files to create HTTP fixtures.
import {withServer, setupServer, startServer, record} from 'tape-six/server.js';import test from 'tape-six';
import {withServer, record} from 'tape-six/server.js';
test('client sends the right requests', async t => {
const rec = record(); // records every request, answers 204
await withServer(rec, async base => {
await fetch(base + '/upload', {
method: 'POST',
headers: {'content-type': 'application/json'},
body: JSON.stringify({a: 1})
});
t.equal(rec.requests.length, 1, 'one call made');
t.match(rec.requests[0], {method: 'POST', url: '/upload', body: '{"a":1}'});
});
});port: 0 (the default) asks the OS for a free port, so parallel test files get collision-free servers by construction.
Scoped resource for a single test: creates an http.Server with serverHandler, starts it, runs clientHandler(base, lifecycle), and closes the server in finally — cleanup runs whether clientHandler resolves, rejects, or throws. Returns the clientHandler result.
-
serverHandler— a standardnode:httprequest listener(req, res). Node calls it once per incoming request. -
clientHandler(base, lifecycle)— the test body;baseis the bound URL, e.g."http://127.0.0.1:54321". -
opts—{host = '127.0.0.1', port = 0}.
Suite-shared variant: registers beforeAll to start the server and afterAll to close it, and returns a frozen context with live getters — server, base, port, host. Don't destructure it at module load: the properties read the running server at access time, and destructuring captures stale undefined values.
Per-test state reset stays on your side (compose your own beforeEach); setupServer owns the lifecycle, the caller owns state.
The procedural primitive under both helpers: takes an existing http.Server, starts listening, and resolves to a lifecycle handle {server, base, port, host, close}. It races 'listening' against 'error', so a busy port or EACCES rejects instead of hanging. close() is idempotent and calls server.closeAllConnections() (when available) so keep-alive sockets don't delay teardown.
Recording wrapper: returns a request listener that captures every request it serves onto its own requests array, then answers 204 — or delegates to handler when one is given.
Each entry is a plain object:
interface RecordedRequest {
method: string;
url: string;
headers: Record<string, string | string[] | undefined>; // lower-cased names
body: string; // eagerly buffered UTF-8 text; '' when empty
}-
Eager by design: the body is fully read before anything responds, so entries are always complete plain objects — assert with
t.matchandt.anywildcards. -
Delegates read
entry.body: ahandler(req, res, entry)receives the captured entry as its third argument; thereqstream is already drained — don't try to read it again. -
Reset between tests with
rec.requests.length = 0(e.g. in abeforeEachwhen paired withsetupServer).
const rec = record((req, res, entry) => {
res.setHeader('content-type', 'text/plain');
res.end('len:' + entry.body.length);
});- The harness is built on
node:http, which Node, Bun, and Deno all provide — one implementation, three CLI runtimes. It is not for browsers: keep tests that import it under a CLI-only pattern (theclitest set in yourtape6config) so browser runs don't pick them up. See Set up tests. - The default host is an explicit
'127.0.0.1'rather than'localhost', avoiding dual-stack surprises on macOS wherelocalhostmay resolve to::1.
-
A test hangs waiting for the server — it won't:
startServerrejects on'error'(port busy, permissions). If you pass a fixedport, prefer0unless the test genuinely needs a known port. -
setupServervalues areundefined— the context was destructured at module load, beforebeforeAllran. Keep the object and readctx.baseinside tests. -
A
record()delegate sees an empty request stream — expected: the body was buffered eagerly; readentry.bodyinstead. -
Teardown is slow with keep-alive clients —
close()already force-closes connections on runtimes that supportcloseAllConnections(); on others, make sure clients don't hold sockets open past the test.
-
Tester —
t.matchandt.anypair naturally with recorded entries. - test() — registering tests and hooks.
-
Writing plugins — when a helper should (and shouldn't) become a
t.*method. - tape6-server — the browser-test web server (a different tool).
-
Response helpers source —
asText/asJson/asBytes/header/headersfor reading bothResponseandIncomingMessage. - Reference tests: tests/cli/test-server.js, tests/cli/test-record.js.
Core API
Third-party libraries
Command-line utilities
Environments
Project
Sisters: proc · puppeteer · playwright