-
Notifications
You must be signed in to change notification settings - Fork 0
services mock
io.mock intercepts requests with your own handlers before they reach the network. It composes with the real pipeline — retries, cache, and decoding still apply — so a mock behaves like an actual server, no HTTP server required.
import io from 'double-meh';
io.mock('https://example.com/a', () => ({mocked: true}));
await io.get('https://example.com/a'); // {mocked: true}
io.mock.clear();Registering a handler auto-attaches the service, so you rarely call attach() yourself.
io.mock(matcher, handler) accepts four matcher shapes:
- an exact URL string — canonicalized (sorted query, dropped fragment), so query order does not matter,
- a trailing-
*prefix string, - a
RegExptested against the URL, - a function
(request, ctx) => boolean.
io.mock('https://example.com/exact', () => ({ok: true}));
io.mock('https://example.com/users/*', request => ({url: request.url}));
io.mock(/\/things\/\d+$/, () => ({matched: true}));
io.mock(
request => request.method === 'POST',
() => ({posted: true})
);A handler receives (request, ctx). Return a Response to use it verbatim, or return any value to have it serialized as a 200 application/json body.
io.mock(
'https://example.com/plain',
() => new Response('hi', {status: 201, headers: {'content-type': 'text/plain'}})
);
const env = await io.full.get('https://example.com/plain');
env.status; // 201, env.data === 'hi'A mock sits at the transport end, so the retry and cache services wrap it. A mock that returns 503 then 200 is retried just like a real flaky endpoint.
let n = 0;
io.mock(
'https://example.com/flaky',
() =>
new Response(JSON.stringify({ok: true}), {
status: ++n < 2 ? 503 : 200,
headers: {'content-type': 'application/json'}
})
);
await io.get('https://example.com/flaky', null, {retry: {retries: 2, initDelay: 0}}); // retried, then {ok: true}Unmatched requests fall through to the real transport. A request can also opt out of mocking entirely with mock: false — it goes straight through even when a matcher would hit.
await io.get('https://example.com/a', null, {mock: false}); // bypasses all mocksCall io.mock(matcher) with the handler omitted to remove a single matcher. io.mock.clear() removes every mock and detaches the service; io.mock.detach() / io.mock.attach() toggle it without dropping registrations.
io.mock('https://example.com/a'); // remove just this one
io.mock.clear(); // remove all + detachBecause a function matcher can match everything, a single catch-all mock becomes a stand-in server. The test suite's serve() helper does exactly this:
const serve = handler => io.mock(() => true, handler);
// in a test:
let calls = 0;
serve(() => json({n: ++calls}));
await io.get('https://example.com/anything'); // handled in-process
io.mock.clear();The handler is a plain function of (request, ctx), so it can branch on method, URL, or headers to emulate whatever server behavior a test needs.
Guides
Concepts
Services
Modules
- fetch transport
- keys & URLs
- helpers
- records
- sse
- storage backends
- compression encoders
- Service Worker integration
Cookbook