@fetchkit/chaos-fetch

Inject network chaos directly into fetch

chaos-fetch is a composable middleware client for fault injection at fetch call sites. You can model latency, random failures, failNth patterns, rate limits, throttling, and mocks with route-level control.

chaos-fetch npm version chaos-fetch npm downloads chaos-fetch github stars

What it does

chaos-fetch wraps fetch with a composable middleware pipeline. You define global rules that apply to every request and route-specific rules matched by method and path. Middleware nodes are executed in order - global first, then route-specific. The real fetch only runs if nothing short-circuits it, which means you can fully mock responses in tests without any network at all.

Route-level control

Different chaos rules per method and path pattern. A flaky GET /users/:id and a rate-limited POST /orders can coexist in one client.

Drop-in replacement

Pass the chaos client wherever fetch is expected, or call replaceGlobalFetch() to intercept all requests globally without touching app code.

Extensible registry

Register custom middleware with registerMiddleware() and use it alongside built-in primitives in any config.

How to use it in testing

Create a chaos client in your test file and pass it directly to the code under test instead of the real fetch. No service worker, no network interception setup - just a function that behaves like fetch but follows the rules you define. Each test can use its own client with different chaos settings.

Note: this pattern works best when your app code accepts an injectable fetch - for example fetchUser(id, { fetch }). If your code calls the global fetch directly, use replaceGlobalFetch(chaosFetch) instead and restore it in afterEach.

import { describe, it, expect } from 'vitest'
import { createClient } from '@fetchkit/chaos-fetch'
import { fetchUser } from './api' // your app code that calls fetch

describe('fetchUser resilience', () => {
  it('retries and succeeds after a transient failure', async () => {
    const chaosFetch = createClient({
      routes: {
        'GET /users/:id': [
          { failFirstN: { n: 1, status: 503 } }, // first call fails, then passes
        ],
      },
    })

    // pass the chaos client into your app code
    const user = await fetchUser(123, { fetch: chaosFetch })
    expect(user.id).toBe(123)
  })

  it('throws when the circuit is open', async () => {
    const chaosFetch = createClient({
      global: [
        { fail: { status: 500 } }, // every request fails
      ],
    })

    await expect(fetchUser(123, { fetch: chaosFetch })).rejects.toThrow()
  })
})

chaos-fetch vs chaos-proxy

Both tools inject chaos, but at different layers. chaos-fetch operates inside the JavaScript runtime as a fetch wrapper - it is ideal for unit and integration tests where you control the code. chaos-proxy is an external HTTP proxy process - it sits between your app and the network, so it works for any HTTP client in any language and is better suited for end-to-end testing or multi-service scenarios.

Capability chaos-fetch chaos-proxy
Works with any HTTP client / language No - fetch only Yes
Setup required None - import and use Run proxy process + point clients at it
Use in unit / integration tests Ideal Possible but heavyweight
Use in end-to-end / multi-service tests Not applicable Ideal
Route-specific chaos rules Yes Yes
Hot-reload config No - create new client Yes
Observability (tracing / metrics) Optional OTEL middleware Built-in

Built-in primitives

  • latency - fixed delay every request
  • latencyRange - random delay between min and max
  • fail - always return a fixed error status
  • failRandomly - fail at a given probability (0..1)
  • failNth - fail every Nth request, cyclically
  • failFirstN - fail the first N requests, then pass
  • rateLimit - cap requests per time window, return 429
  • throttle - limit response bandwidth in bytes/sec
  • mock - return a fixed response without hitting the network

Highlights

  • Global + route-specific middleware chains
  • Koa-style composable middleware model
  • Custom middleware registry
  • Optional global fetch replacement

From the blog