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.

Why two response shapes

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() returns Promise<string>; IncomingMessage requires manual 'data'/'end' event handling.
  • Response.headers is a Headers object with .get(); IncomingMessage.headers is 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.

asText()

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.

asJson()

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.

asBytes()

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 byte

For Response, delegates to res.arrayBuffer() and wraps. For IncomingMessage, drains the stream into a Uint8Array.

header()

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 , .

headers()

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.

Why no status-code helper

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.

⚠️ **GitHub.com Fallback** ⚠️