See also: API Reference
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 madeonce - Shorthand for times: 1With 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';
createTrackedServer(...handlers)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
}
isTrackedServer(value)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
waitForBodies(server)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 serversclearTrackedRequests() - Clears all tracked request history[Symbol.dispose]() - Calls close() (enables using syntax)Copyright © 2026 Christopher "boneskull" Hiller. Licensed under BlueOak-1.0.0.