Q18 of 37 · API testing
How do you mock external services your API depends on?
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
fetchcalls 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
// COMMON PITFALL
// Related questions
Explain how cy.fixture() works and when to use it vs cy.intercept stubbing.
Cypress
How do you intercept and mock network requests in Playwright?
Playwright
How do you mock external APIs that your service calls during a REST Assured test?
REST Assured
Compare Karate's Mock Server to WireMock — when would you choose each?
Karate