Q18 of 37 · API testing

How do you mock external services your API depends on?

API testingMidapimockingwiremockmsw

Short answer

Short answer: Stand up a stub server (WireMock, Mockoon, MSW, nock) that mimics the third-party's contract. Point your API at the stub via env-var URLs. Record-replay the real responses for fidelity. For some integrations, use the provider's sandbox (Stripe, Twilio test keys) instead of mocking.

Detail

Most APIs depend on something external — payments, email, geolocation, search. Testing against the real third party is slow, expensive, and prone to flake from upstream changes. The right answer is a layered strategy.

Layer 1 — Provider sandboxes (when available). Stripe test mode, Twilio test credentials, AWS LocalStack. Use the provider's own test environment. Pros: high fidelity, the provider maintains the behaviour. Cons: still requires network, sometimes rate-limited.

Layer 2 — In-process mocks (best for unit / component tests):

  • MSW (Mock Service Worker) — intercepts fetch calls in JS tests.
  • nock (Node) — same idea, mocking native HTTP.
  • WireMock standalone — JVM tool with stubbing DSL.
  • Mockoon — GUI-driven, JSON-configurable mock server.
  • httpx mock / responses (Python) — patch HTTP at the library level.
// MSW example
import { rest } from 'msw';
const server = setupServer(
  rest.post('https://api.stripe.com/v1/charges', (req, res, ctx) =>
    res(ctx.status(200), ctx.json({ id: 'ch_test', status: 'succeeded' }))
  ),
);

Layer 3 — Out-of-process mock servers (best for E2E):

  • WireMock in Docker, configured via mappings.
  • Mockoon with a saved environment file.
  • Pact mock provider for contract-driven E2E.

The API under test is configured via env var:

STRIPE_API_URL=http://localhost:9090  # WireMock instance

This is what you'd run in CI E2E tests.

Layer 4 — Record-and-replay. Tools like vcrpy or polly.js record real interactions during test development, then replay them in CI without touching the network. Useful for high-fidelity stubs of complex APIs.

Pitfalls:

1. Drifted mocks. The third party changes their API; your mock still pretends nothing happened. Mitigations:

  • Periodic real-API verification tests in nightly.
  • Contract testing (Pact) against the provider, if they publish contracts.

2. Over-mocking. Mocking everything turns tests into "the mock returns what we said it would." Cover at least one E2E that hits a real sandbox to catch drift.

3. Mocked latency. Real third parties are slow / rate-limited / occasionally down. Tests against fast mocks miss timeout-handling bugs. Inject artificial latency or failures (response.delay(2000)) to test resilience.

4. State leakage. Mock servers that persist state across tests cause flaky behaviour. Reset mocks in beforeEach.

Anti-pattern: end-to-end tests that hit real third-party services in CI. Network outages and provider rate limits will flake your suite. Mock for CI, real for nightly verification.

// EXAMPLE

stripe.mock.test.ts

import { setupServer } from 'msw/node';
import { rest } from 'msw';

const server = setupServer(
  rest.post('https://api.stripe.com/v1/charges', async (req, res, ctx) => {
    return res(
      ctx.delay(150),                       // simulate real-world latency
      ctx.status(200),
      ctx.json({ id: 'ch_test_123', status: 'succeeded' }),
    );
  }),
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

test('checkout returns charge id', async () => {
  const res = await fetch('http://localhost:3000/api/checkout', {
    method: 'POST',
    body: JSON.stringify({ amount: 1000 }),
  });
  expect(res.status).toBe(200);
  expect((await res.json()).chargeId).toBe('ch_test_123');
});

// WHAT INTERVIEWERS LOOK FOR

Layered strategy (sandboxes → in-process mocks → out-of-process mock servers → record-replay), naming 2-3 specific tools, and the wisdom of injecting failures to test resilience.

// COMMON PITFALL

Mocking the third party once and never re-validating. Six months later the real API has changed, your mock still shows green, and prod breaks. Periodic real-API verification is non-negotiable.