Module ‐ response - uhop/tape-six GitHub Wiki
The tape-six/response module provides reading helpers that work uniformly across both Response (fetch results) and Node's http.IncomingMessage. Tests can read a body or inspect headers without branching on which kind of response object they hold.
import {asText, asJson, asBytes, header, headers} from 'tape-six/response';The five helpers cover the common assertion needs: read body in three formats, read individual headers, dump all headers as a plain object. For the status code, just use res.status (W3C Response) or res.statusCode (Node IncomingMessage) directly — see Why no status-code helper below.
A test calling fetch() gets a W3C Response. A test using Node's lower-level http.request() callback gets an http.IncomingMessage (a readable stream). Their APIs diverge:
-
Response.text()returnsPromise<string>;IncomingMessagerequires manual'data'/'end'event handling. -
Response.headersis aHeadersobject with.get();IncomingMessage.headersis a plain object with lowercase keys, where some values may be arrays (e.g.set-cookie).
These helpers normalize both into a single shape so test code stays the same regardless of which response it holds.
function asText(res: ResponseLike): Promise<string>;Reads the body as a UTF-8 string.
const body = await asText(res);
t.equal(body, 'hello world');For Response, delegates to res.text(). For IncomingMessage, drains the stream and decodes.
function asJson<T = unknown>(res: ResponseLike): Promise<T>;Reads the body and parses as JSON.
const body = await asJson(res);
t.deepEqual(body, {ok: true, data: [1, 2, 3]});Equivalent to JSON.parse(await asText(res)); uses res.json() directly when available.
function asBytes(res: ResponseLike): Promise<Uint8Array>;Reads the body as raw bytes.
const buf = await asBytes(res);
t.equal(buf.length, 1024);
t.equal(buf[0], 0x89); // PNG magic byteFor Response, delegates to res.arrayBuffer() and wraps. For IncomingMessage, drains the stream into a Uint8Array.
function header(res: ResponseLike, name: string): string | null;Reads a single header by name, case-insensitive. Returns null if the header isn't present (matching Response.headers.get semantics).
const ct = header(res, 'content-type');
t.match(ct, /^application\/json/);For array-valued IncomingMessage headers (e.g. set-cookie), values are joined with , .
function headers(res: ResponseLike): Record<string, string>;Returns all response headers as a plain object with lowercase keys.
const all = headers(res);
t.equal(all['content-type'], 'application/json; charset=utf-8');
t.equal(all['content-length'], '42');Array-valued headers are joined with , to match Response.headers shape.
The W3C Response exposes the status as res.status; Node's IncomingMessage exposes it as res.statusCode. They differ in spelling but both are direct properties — no async read needed. Tests that need to be portable across both response shapes can use res.status ?? res.statusCode. A wrapper for one property hides nothing and adds a layer of indirection, so the module deliberately doesn't ship one.
Pairs naturally with tape-six/server
import {withServer} from 'tape-six/server';
import {asJson, header} from 'tape-six/response';
test('GET / returns JSON', t =>
withServer(handler, async base => {
const res = await fetch(`${base}/`);
t.equal(res.status, 200);
t.match(header(res, 'content-type'), /^application\/json/);
const body = await asJson(res);
t.deepEqual(body, {ok: true});
}));tape-six/server gives the test setup; tape-six/response gives the assertion-side helpers. Either is usable independently — these helpers also work fine with fetch against any other server you bring.