Key insight

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.

The combination

MSW handles what comes back. slowdep handles how long it takes. Together they give you realistic integration test behavior without any real external services.

Feature comparison

What each tool does

featureMSWslowdep
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
Composing

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.
Non-HTTP

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 });
Full-stack realistic testing

MSW for HTTP APIs + slowdep for databases and caches = a complete realistic dependency layer with no real external services required.