BUPKIS
    Preparing search index...

    See also: API Reference

    @bupkis/msw

    MSW (Mock Service Worker) request verification assertions for Bupkis.

    npm install @bupkis/msw bupkis msw -D
    
    import { use } from 'bupkis';
    import mswAssertions, { createTrackedServer } from '@bupkis/msw';
    import { http, HttpResponse } from 'msw';

    const { expect, expectAsync } = use(mswAssertions);

    // Create a tracked server instead of using setupServer directly
    // `using` automatically calls server.close() when the block exits
    {
    using server = createTrackedServer(
    http.get('/api/users', () => HttpResponse.json([{ id: 1, name: 'Alice' }])),
    http.post('/api/users', () => HttpResponse.json({ id: 2 })),
    );

    server.listen();

    // Make requests
    await fetch('https://api.example.com/api/users');
    await fetch('https://api.example.com/api/users', {
    method: 'POST',
    body: JSON.stringify({ name: 'Bob' }),
    headers: { 'content-type': 'application/json' },
    });

    // Verify requests were handled (sync - use expect)
    expect(server, 'to have handled request to', '/api/users');
    expect(server, 'to have handled request to', '/api/users', {
    method: 'POST',
    });

    // For body matching, use expectAsync - it awaits body parsing automatically
    await expectAsync(server, 'to have handled request to', '/api/users', {
    method: 'POST',
    body: { name: 'Bob' },
    });

    // Verify request count
    expect(server, 'to have handled', 2, 'requests');

    // Clear tracked requests between tests
    server.clearTrackedRequests();

    // server.close() called automatically here!
    }

    Asserts that the server handled a request to the specified path.

    Success:

    await fetch('https://api.example.com/api/users');
    expect(server, 'to have handled request to', '/api/users');

    Failure:

    expect(server, 'to have handled request to', '/api/unknown');
    // AssertionError: Expected server to have handled request to "/api/unknown"

    Asserts that the server handled a request matching the path and options.

    Options:

    • method - HTTP method to match (case-insensitive)
    • body - Expected request body (uses "to satisfy" semantics)
    • headers - Expected headers (string for exact match, RegExp for pattern)
    • times - Exact number of times the request should have been made
    • once - Shorthand for times: 1

    With method:

    await fetch('https://api.example.com/api/users', {
    method: 'POST',
    body: '{}',
    });
    expect(server, 'to have handled request to', '/api/users', { method: 'POST' });

    With body (use expectAsync):

    await fetch('https://api.example.com/api/users', {
    method: 'POST',
    body: JSON.stringify({ name: 'Bob', extra: 'field' }),
    headers: { 'content-type': 'application/json' },
    });

    // Use expectAsync for body matching - it awaits body parsing automatically
    // Uses "to satisfy" semantics - partial match works
    await expectAsync(server, 'to have handled request to', '/api/users', {
    method: 'POST',
    body: { name: 'Bob' }, // extra field is ignored
    });

    With headers:

    await fetch('https://api.example.com/api/users', {
    headers: { authorization: 'Bearer abc123' },
    });

    // Exact match
    expect(server, 'to have handled request to', '/api/users', {
    headers: { authorization: 'Bearer abc123' },
    });

    // RegExp match
    expect(server, 'to have handled request to', '/api/users', {
    headers: { authorization: /^Bearer / },
    });

    With times:

    await fetch('https://api.example.com/api/users');
    await fetch('https://api.example.com/api/users');
    await fetch('https://api.example.com/api/users');
    expect(server, 'to have handled request to', '/api/users', { times: 3 });

    With once:

    await fetch('https://api.example.com/api/users');
    expect(server, 'to have handled request to', '/api/users', { once: true });

    Negation (assert no request was handled):

    expect(server, 'not to have handled request to', '/api/admin');
    

    Asserts that the server handled a request matching a RegExp pattern.

    Success:

    await fetch('https://api.example.com/api/users/123');
    expect(server, 'to have handled request matching', /\/api\/users\/\d+/);

    Failure:

    expect(server, 'to have handled request matching', /\/api\/admin\/\d+/);
    // AssertionError: Expected server to have handled request matching /\/api\/admin\/\d+/

    Asserts that the server handled a request matching a RegExp pattern with options.

    await fetch('https://api.example.com/api/users/123', { method: 'DELETE' });
    expect(server, 'to have handled request matching', /\/api\/users\/\d+/, {
    method: 'DELETE',
    });

    Negation (assert no request matched):

    expect(server, 'not to have handled request matching', /\/api\/admin/);
    

    Asserts that the server handled exactly the specified number of requests.

    Success:

    await fetch('https://api.example.com/api/users');
    await fetch('https://api.example.com/api/users/1');
    await fetch('https://api.example.com/api/users/2');
    expect(server, 'to have handled', 3, 'requests');

    Failure:

    await fetch('https://api.example.com/api/users');
    expect(server, 'to have handled', 5, 'requests');
    // AssertionError: Expected server to have handled 5 request(s), but handled 1

    Asserts that the server has handled at least one request.

    Success:

    await fetch('https://api.example.com/api/users');
    expect(server, 'to have handled requests');

    No requests (use negation):

    expect(server, 'not to have handled requests');
    
    // Assertions (default export)
    export { mswAssertions as default, mswAssertions } from './assertions.js';

    // Type guard
    export { isTrackedServer } from './guards.js';

    // Zod schemas
    export { PathMatcherSchema, TrackedServerSchema } from './schema.js';

    // Tracker factory and utilities
    export { createTrackedServer, waitForBodies } from './tracker.js';

    // Types
    export type {
    PathMatcher,
    RequestMatchOptions,
    TrackedRequest,
    TrackedServer,
    } from './types.js';

    Creates an MSW server with request tracking capabilities. This is a drop-in replacement for setupServer from msw/node.

    The returned server implements Disposable, so you can use using syntax (TypeScript 5.2+) for automatic cleanup:

    import { createTrackedServer } from '@bupkis/msw';
    import { http, HttpResponse } from 'msw';

    {
    using server = createTrackedServer(
    http.get('/api/users', () => HttpResponse.json([])),
    );
    server.listen();
    // ... make requests and assertions ...
    // server.close() called automatically when block exits
    }

    Type guard that checks if a value is a TrackedServer instance.

    import { isTrackedServer, createTrackedServer } from '@bupkis/msw';
    import { setupServer } from 'msw/node';

    const trackedServer = createTrackedServer();
    const plainServer = setupServer();

    isTrackedServer(trackedServer); // true
    isTrackedServer(plainServer); // false

    Waits for all tracked request bodies to be parsed and returns the requests.

    Note: When using expectAsync for body matching, this is called automatically. Use this when you need to access req.body directly.

    await fetch(url, { method: 'POST', body: JSON.stringify({ name: 'Bob' }) });

    // Returns requests with bodies resolved
    const requests = await waitForBodies(server);
    console.log(requests[0].body); // { name: 'Bob' }

    // For body assertions, prefer expectAsync (body parsing is automatic)
    await expectAsync(server, 'to have handled request to', '/api', { body: data });
    • trackedRequests - Array of all tracked requests (includes bodyPromise for each request)
    • isTrackedServer - Always true for tracked servers
    • clearTrackedRequests() - Clears all tracked request history
    • [Symbol.dispose]() - Calls close() (enables using syntax)

    Copyright © 2026 Christopher "boneskull" Hiller. Licensed under BlueOak-1.0.0.