The gap

Why CI latency matters

There's a systematic gap between CI and production: your unit tests run with zero-latency mocks, integration tests run against local services, and then production hits real network latency, cache misses, and tail behavior. The bugs that live in this gap — timeout misconfiguration, retry storms, circuit breakers that never trip — only surface in production.

Adding slowdep to your CI pipeline closes this gap. A dedicated "latency tests" CI step runs your integration tests with realistic latency profiles, catching the class of bugs that zero-latency tests structurally cannot find.

The rule of thumb

If your test suite would still pass if every dependency responded in 0ms, you're not testing latency-dependent behavior. Add slowdep to tests where latency characteristics affect correctness.

Configuration

Configuring slowdep for CI

Keep latency disabled locally by default so developer iteration stays fast. Enable it in CI via an environment variable. This pattern lets you run the full test suite quickly on a developer machine while ensuring latency tests run on every CI build.

// lib/testHelpers.ts
import { withLatency } from 'slowdep';

const LATENCY_ENABLED = process.env.SLOWDEP_ENABLED === 'true';

/**
 * Wraps a function with realistic latency when SLOWDEP_ENABLED=true.
 * In local development (default), returns the function unchanged.
 */
export function maybeSlowdep<T extends (...args: any[]) => Promise<any>>(
  fn: T,
  preset: Parameters<typeof withLatency>[1]
): T {
  return LATENCY_ENABLED ? withLatency(fn, preset) : fn;
}

// usage in tests
const db = maybeSlowdep(realDb.query, 'postgres');
// local: instant  |  CI (SLOWDEP_ENABLED=true): lognormal p50:5ms p99:200ms
Test selection

Which tests to add latency to

Not every test benefits from realistic latency. Apply it selectively where latency characteristics actually affect whether the test is meaningful.

test typeadd slowdep?reason
unit tests no unit tests mock dependencies; latency adds noise without signal
integration tests yes tests real code paths with realistic dependency behavior
retry / timeout tests yes these specifically test latency-dependent behavior
e2e tests selectively add for specific scenarios (slow network, timeout UX); not all flows
snapshot tests no UI snapshots don't depend on latency timing
circuit breaker tests yes circuit breakers trip on slow failures, not fast ones
Time budget

CI time budget

Realistic latency slows down tests — that's the point, but it needs to be managed. A test that takes 5ms without slowdep might take 200ms with it. At 1000 tests, that's 200 seconds of added time. These strategies keep CI fast.

1
Separate CI step for latency tests
Tag latency-sensitive tests (e.g., @latency Jest tag or a separate *.latency.test.ts suffix) and run them as a parallel CI job. Regular tests remain fast; latency tests run alongside them.
2
Use lower p99 in CI vs staging
In CI, configure p99 at 2–5× p50 rather than the full production p99. This exercises the distribution shape without adding extreme tail waits. Reserve full production p99 for staging environment tests.
3
Parallelize test suites
Use Jest's --maxWorkers or Vitest's --pool=threads to run test files in parallel. Latency tests benefit most from parallelization since they spend most time waiting.
Reproducibility

Deterministic CI

Probabilistic latency means tests can occasionally fail when tail samples are sampled. For deterministic CI, seed Math.random at the start of each CI run. The same seed produces the same latency samples every time, making failures reliably reproducible.

// jest.setup.ts — seed Math.random for deterministic CI
if (process.env.SLOWDEP_SEED) {
  const seed = parseInt(process.env.SLOWDEP_SEED, 10);
  // simple seeded PRNG (use a proper implementation in production)
  let s = seed;
  Math.random = () => {
    s = (s * 1664525 + 1013904223) >>> 0;
    return s / 0xFFFFFFFF;
  };
}

// then in CI: SLOWDEP_SEED=12345 jest
// same seed = same latency samples = reproducible failures
GitHub Actions

Example CI config

A GitHub Actions workflow that runs unit tests and latency tests as parallel jobs. Latency tests only run when there are changes to files that exercise latency-dependent behavior.

# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  unit-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm test
        env:
          NODE_ENV: test

  latency-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm run test:latency
        env:
          NODE_ENV: test
          SLOWDEP_ENABLED: 'true'
          SLOWDEP_SEED: '42'
        timeout-minutes: 10
package.json scripts

Add "test:latency": "jest --testPathPattern=\\.latency\\.test\\.ts$ --maxWorkers=4" to your package.json. Latency tests live in *.latency.test.ts files alongside your regular tests.