Complementary, not competing
MSW (Mock Service Worker) and slowdep solve different problems. MSW intercepts HTTP requests and returns mock responses — it replaces the content of API calls. slowdep adds realistic latency to function calls — it replaces the timing of those calls.
Used together, they give you the best of both worlds: MSW provides the mock response (no real API needed), and slowdep adds the latency profile that makes the mock behave like the real thing from a timing perspective.
MSW handles what comes back. slowdep handles how long it takes. Together they give you realistic integration test behavior without any real external services.
What each tool does
| feature | MSW | slowdep |
|---|---|---|
| what it intercepts | HTTP requests (fetch, XMLHttpRequest) | any async function call |
| latency simulation | manual — add delay: in resolver | automatic — lognormal distribution |
| response mocking | full — status, headers, body | none — passes through to real function |
| works in Node.js | yes — msw/node | yes |
| works in browser | yes — service worker | yes — wraps fetch or SDK functions |
| non-HTTP dependencies | no — HTTP only | yes — Postgres, Redis, any async fn |
| error injection | yes — return error responses | yes — errorRate option |
| composability | use alongside slowdep | use alongside MSW |
Using MSW and slowdep together
The composition pattern: MSW intercepts HTTP and returns a fixture. slowdep wraps the HTTP client function to add latency before the MSW handler is even invoked. Or: use MSW as the mock layer and wrap it with slowdep at the SDK level.
// Approach 1: wrap the SDK function with slowdep // MSW handles the response; slowdep adds latency at the SDK level import { withLatency } from 'slowdep'; import { http, HttpResponse } from 'msw'; import { setupServer } from 'msw/node'; const server = setupServer( http.get('/api/users/:id', () => HttpResponse.json({ id: '1', name: 'Alice' })) ); const realisticFetch = withLatency( (url: string) => fetch(url).then(r => r.json()), { p50: 80, p99: 1000 } // HTTP preset: p50 80ms, p99 1s ); it('loads user with realistic HTTP latency', async () => { // MSW returns the fixture; slowdep adds the delay const user = await realisticFetch('/api/users/1'); expect(user.name).toBe('Alice'); });
// Approach 2: add delay directly in the MSW handler // Uses MSW's built-in delay helper (fixed delay, not lognormal) import { http, HttpResponse, delay } from 'msw'; const server = setupServer( http.get('/api/users/:id', async () => { await delay(200); // fixed 200ms — not lognormal return HttpResponse.json({ id: '1', name: 'Alice' }); }) ); // Note: MSW's delay is a fixed value, not a distribution. // For realistic latency, prefer Approach 1 (wrap the fetch function). // Use this approach when you only care about "some delay" and not // testing latency-dependent behavior like retries or timeouts.
For non-HTTP dependencies, use slowdep directly
MSW only intercepts HTTP. For Postgres, Redis, MongoDB, S3, or any other non-HTTP dependency, slowdep is the right tool. No other tool in the JavaScript ecosystem gives you realistic lognormal latency for database client function calls.
import { withLatencyAll } from 'slowdep'; // MSW handles HTTP mocking // slowdep handles everything else const db = withLatencyAll(mockPgClient, 'postgres'); // p50:5ms p99:200ms const cache = withLatencyAll(mockRedisClient, 'redis'); // p50:1ms p99:20ms const bucket = withLatencyAll(mockS3Client, 's3'); // p50:30ms p99:500ms // your service uses real-feeling dependencies, no actual infra const svc = new UserService({ db, cache, bucket });
MSW for HTTP APIs + slowdep for databases and caches = a complete realistic dependency layer with no real external services required.