Set out the behavior
The client promises to return parsed JSON for a successful response and reject an unsuccessful response or invalid body. Write checks against those visible results. Avoid asserting the function's internal variable names or the exact order of harmless implementation steps; those details should be free to change during a refactor.
Control the ingredients
The fake fetch functions below return standard Response objects. Each test gets its own response because a response body is consumed when read. The reserved example.invalid address documents that this is a fixture; it is never contacted because fetchImpl is supplied. Save the test beside client.mjs and run the command shown below.
Use failures as information
Expect all three checks to pass for the client in the API recipe. If one fails, inspect the assertion and actual error before changing code. These checks do not cover timeout behavior, authentication, network redirects, or your application's response schema. Add focused cases for those concerns when you introduce them rather than treating three passing tests as complete coverage.
Recipe 04 / Test batch
Taste the failure cases, too.
This batch tests the JSON client from the previous recipe using the built-in Node.js test runner. Each response is supplied locally, so no live service or credential is involved.
client.test.mjs
import test from 'node:test';
import assert from 'node:assert/strict';
import { getJson } from './client.mjs';
const url = 'https://example.invalid/data';
test('returns parsed JSON', async () => {
const result = await getJson(url, {
fetchImpl: async () => new Response('{"count":2}', { status: 200 })
});
assert.deepEqual(result, { count: 2 });
});
test('rejects unsuccessful HTTP status', async () => {
await assert.rejects(getJson(url, {
fetchImpl: async () => new Response('Unavailable', { status: 503 })
}), /HTTP 503/);
});
test('rejects malformed JSON', async () => {
await assert.rejects(getJson(url, {
fetchImpl: async () => new Response('not json', { status: 200 })
}), SyntaxError);
});Expected result
Expected result: 3 passing tests, 0 failing tests.
Run the recipe
node --test client.test.mjs
The serving check
All three tests pass without any network request. The tests still fail if the client stops rejecting HTTP errors or malformed JSON.
Next at the counterDebug a recipe