Mocking and Stubbing APIs for Reliable Tests

Testing · Updated October 2026 · ← All tips

A test suite that calls a real third-party payment gateway, a flaky internal service, or an API that allows only 100 requests a day is not a test suite — it's a liability. Mocking and stubbing let you test your own code against a dependency without depending on that dependency being up, fast, or free. Done well, this makes suites faster and deterministic. Done badly, it makes them lie about what works in production. Here's how to tell the difference.

1. Stub, mock and fake are not the same thing

The terms get used interchangeably, but the distinction matters when you're deciding what to build:

Most API test suites only need stubs: fixed responses for fixed scenarios. Reach for mocks when you need to verify a call happened (did we actually send the webhook retry?); reach for fakes when a single stubbed response can't represent a multi-step flow (create → fetch → update).

2. Decide what you're actually testing before you mock anything

Mocking removes the real dependency from the test — that's the point, but it also means the test can't catch anything that depends on the dependency behaving as assumed. Use this split to decide when mocking is the right call:

You're testing...Mock the dependency?
Your error handling when a payment provider times outYes — you can't reliably force a real timeout
Your retry logic on a 503Yes — forcing a real 503 on demand isn't practical
Whether your integration with the provider actually works end to endNo — mocking this just tests your assumptions about the provider, not the provider
A third-party API with a hard rate limit or per-call costYes for most runs, with a small smoke suite against the real thing
An internal service your team owns and can run locallyPrefer running it for real in CI; mock only for unit-level isolation

The rule of thumb: mock dependencies you don't control or can't reliably exercise; keep at least a small number of tests hitting the real thing so mocked assumptions get checked against reality on a schedule, not never.

3. Three places to put the mock

For API client testing specifically, the local mock server is usually the sweet spot: define a RestRuno environment with baseUrl = http://localhost:4010 for "mocked" and baseUrl = https://api.example.com for "live," and run the exact same collection against either — no changes to the requests themselves.

4. A minimal mock server, two ways

If the real API has an OpenAPI spec, generate the mock from it instead of hand-writing responses — this is the single biggest thing you can do to stop mocks drifting from reality:

# Prism mocks straight from an OpenAPI document
npx @stoplight/prism-cli mock openapi.yaml --port 4010

Without a spec, a few explicit stubs are often enough:

// WireMock-style stub mapping
{
  "request": { "method": "GET", "urlPath": "/v1/orders/42" },
  "response": {
    "status": 200,
    "jsonBody": { "id": 42, "status": "shipped", "total": 58.90 },
    "headers": { "Content-Type": "application/json" }
  }
}

// Also stub the failure path you can't trigger on demand
{
  "request": { "method": "GET", "urlPath": "/v1/orders/999" },
  "response": { "status": 503, "jsonBody": { "error": "upstream_unavailable" } }
}

5. Keep mocks from lying

A stale mock is worse than no mock — it reports green while production is red. Guard against drift:

Quick checklist

Switch between mocked and live in one click

RestRuno's environments let you point the same collection at a local mock server or the real API by switching a variable — no edits to requests. Free, local-first, and your collections stay as plain JSON files you can commit.

Download RestRuno — free