How to Mock External APIs in Tests: The Definitive Guide
Back to Blog
EngineeringAPI TestingMockingSoftware Architecture

How to Mock External APIs in Tests: The Definitive Guide

External API dependencies can turn your CI/CD pipeline into a bottleneck. Learn the industry-standard strategies for mocking external APIs to build resilient, fast, and cost-effective test suites.

March 17, 202612 min read

In the modern era of microservices and cloud-native architectures, your application is rarely a lonely island. It is more like a hub in a massive, global network of APIs. Whether you are processing payments via Stripe, sending notifications through Twilio, or fetching user data from a legacy CRM, your code depends on external systems you do not control.

But here is the billion-dollar question: How do you test your code when the systems it depends on are unreliable, slow, or expensive to call?

If your CI/CD pipeline fails because a third-party sandbox is down, or if you are paying thousands of dollars in API usage fees just to run your unit tests, you have a mocking problem. At Increments Inc., having built complex platforms for global leaders like Freeletics and Abwaab over the last 14+ years, we have seen how poorly managed external dependencies can cripple a development team's velocity.

In this guide, we will explore the comprehensive landscape of mocking external APIs in tests—from simple in-memory stubs to sophisticated service virtualization.


The Cost of the "Real" Dependency

Before diving into the how, we must understand the why. Why not just hit the actual external API? Many developers argue that testing against the "real thing" is the only way to ensure 100% accuracy. While that sounds good in theory, it fails in practice for four major reasons:

  1. Determinism (or lack thereof): External APIs are non-deterministic. They might return a 500 error, experience latency, or change data between test runs. Tests must be deterministic to be useful.
  2. Speed: A network round-trip to a server across the globe takes hundreds of milliseconds. Multiply that by 1,000 tests, and your build time goes from seconds to hours.
  3. Cost: Many APIs (like Google Maps or OpenAI) charge per request. Running your full test suite 50 times a day can lead to a massive, unnecessary bill.
  4. Edge Case Coverage: It is nearly impossible to force a third-party API to return a specific error code (like a 429 Rate Limit) exactly when you need it for a test case.

At Increments Inc., we believe in "Shift-Left" testing. By mocking these dependencies early, you catch integration bugs before they ever reach a staging environment. If you're struggling with a complex architecture and need a roadmap, we offer a free AI-powered SRS document (IEEE 830 standard) and a $5,000 technical audit for every project inquiry at incrementsinc.com/start-project.


Mocking vs. Stubbing vs. Spying: The Terminology Trap

In the world of testing, these terms are often used interchangeably, but they represent distinct concepts. Understanding the difference is crucial for choosing the right strategy.

Term Purpose Behavior
Stub Provides canned answers Returns fixed data to help the test proceed.
Mock Verifies interactions Focuses on how the API was called (e.g., "Was this endpoint called with the right API key?").
Spy Records activity Acts as a wrapper around the real or stubbed function to track calls and parameters.
Fake Simplified implementation A working but lightweight version of the service (e.g., an in-memory SQLite database instead of PostgreSQL).

For external APIs, we usually combine these. We stub the response data and mock the request verification.


Strategy 1: In-Memory Mocking (The Code Level)

This is the most common approach for unit tests. Instead of making a network call, you replace the function or class responsible for the API call with a mock version.

Example in TypeScript/Jest

Imagine a service that fetches weather data:

// weatherService.ts
import axios from 'axios';

export const getWeather = async (city: string) => {
  const response = await axios.get(`https://api.weather.com/v1/${city}`);
  return response.data;
};

In your test, you don't want axios to actually reach out to weather.com. You mock the module:

// weatherService.test.ts
import axios from 'axios';
import { getWeather } from './weatherService';

jest.mock('axios');
const mockedAxios = axios as jest.Mocked<typeof axios>;

test('should return weather data for a city', async () => {
  mockedAxios.get.mockResolvedValue({ data: { temp: 25, condition: 'Sunny' } });
  
  const data = await getWeather('Dhaka');
  
  expect(data.temp).toBe(25);
  expect(mockedAxios.get).toHaveBeenCalledWith('https://api.weather.com/v1/Dhaka');
});

Pros:

  • Extremely fast.
  • No network configuration needed.
  • Easy to simulate errors.

Cons:

  • Tight coupling to your implementation (if you switch from axios to fetch, your tests break).
  • Doesn't test the actual HTTP layer (headers, serialization, etc.).

Strategy 2: Network Interception (The Library Level)

Network interception is a "cleaner" way to mock. Instead of mocking your code's internal functions, you intercept the outgoing HTTP requests at the library or system level. Tools like Nock (Node.js) or MSW (Mock Service Worker) are the gold standards here.

The Power of MSW (Mock Service Worker)

MSW is revolutionary because it uses Service Workers in the browser (or intercepts in Node) to catch requests at the network level. This means your application code remains completely unaware that it is being mocked.

+---------------------+       +-----------------------+
|  Application Code   |       |    MSW Interceptor    |
|  (fetch/axios call) | ----> | (Matches URL/Method)  |
+---------------------+       +-----------+-----------+
                                          |
                                          v
                              +-----------------------+
                              |  Mocked JSON Response |
                              +-----------------------+

Why Increments Inc. recommends MSW:
It allows you to share the same mock definitions between your development environment (for local UI work) and your automated test suite. This "Single Source of Truth" for mocks reduces the chance of your mocks drifting away from reality.


Strategy 3: Mock Servers (The Infrastructure Level)

When you move into integration and end-to-end (E2E) testing, in-memory mocks aren't enough. You need a real HTTP server that responds like the external API. This is where WireMock, Prism, or Microcks come in.

Comparison of Mock Server Tools

Tool Best For Key Feature
WireMock Java/JVM & Enterprise Highly configurable, supports stateful behavior.
Prism OpenAPI/Swagger Automatically generates mocks from your API spec file.
LocalStack AWS Services Mocks S3, Lambda, DynamoDB, etc., locally.
Mountebank Multi-protocol Supports HTTP, TCP, and SMTP mocking.

Architectural View of a Mock Server

In a CI environment, your architecture looks like this:

      [ CI Runner ]
           |
    +------+------+
    |             |
[ App Container ] [ WireMock Container ]
    |             |
    +---> HTTP ---+ (Port 8080)

By pointing your application's BASE_URL environment variable to the Mock Server instead of the real API, you can run full-system tests in total isolation.

If you are planning a large-scale enterprise migration or building a complex SaaS platform, setting up this infrastructure correctly is vital. At Increments Inc., we specialize in platform modernization and robust DevOps pipelines. Let’s discuss your architecture: Start a Project.


Strategy 4: Contract Testing (The "Gold Standard")

One of the biggest risks of mocking is Mock Drift. This happens when the external API changes (e.g., a field name changes from user_id to uuid), but your mock still uses the old name. Your tests pass, but your production code fails.

Contract Testing solves this by creating a shared agreement (a contract) between the consumer (you) and the provider (the external API).

  1. Consumer-Driven Contracts (Pact): You define what data you expect. This generates a contract file.
  2. Verification: The provider (or a mock representing them) verifies that they can still fulfill that contract.

While harder to set up, contract testing is the only way to ensure that your mocks stay in sync with reality in a fast-moving microservices environment.


Best Practices for Mocking External APIs

After 14 years in the industry, our engineering team at Increments Inc. has distilled mocking down to these core principles:

1. Don't Mock What You Don't Own

This is a classic piece of advice from the "London School" of TDD. Instead of mocking the third-party library directly (like stripe.customers.create), wrap that library in your own interface (e.g., PaymentGateway). Mock your interface, not the library. This protects you if you ever swap providers.

2. Simulate the "Happy Path" AND the "Sad Path"

Too many developers only mock successful 200 OK responses. A robust test suite must include:

  • 401 Unauthorized: Does your app redirect to login?
  • 429 Too Many Requests: Does your app implement retry logic with exponential backoff?
  • 500 Internal Server Error: Does your app show a graceful error message or just crash?
  • Slow Responses: Does your app time out correctly or hang indefinitely?

3. Use Realistic Data

Avoid using test_data_1. Use realistic JSON payloads. If the API you are mocking has an OpenAPI/Swagger spec, use a tool like Prism to generate mocks directly from that spec. This ensures your mock data structures are always valid.

4. Keep Mocks Close to the Test

Global mocks that apply to every test in your suite are a recipe for "Spooky Action at a Distance." It becomes impossible to tell why a test is failing. Define your mock behavior as close to the individual test case as possible.


Case Study: Modernizing a FinTech Platform

A recent client came to Increments Inc. with a major problem: their CI/CD pipeline took 45 minutes to run because it was hitting a legacy banking sandbox for every transaction test. Not only was it slow, but the sandbox would frequently go down, causing "flaky" builds that developers began to ignore.

Our Solution:

  1. We implemented WireMock to record the real banking interactions once.
  2. We converted those recordings into static mappings used in the CI pipeline.
  3. We introduced a $5,000 technical audit (part of our standard onboarding) to identify other bottlenecks in their architecture.

The Result:
Build times dropped from 45 minutes to 6 minutes. Flaky tests were eliminated, and the team's deployment frequency tripled.


Key Takeaways

  • Mocking is essential for deterministic, fast, and cost-effective testing.
  • In-memory mocks (Jest/Mockito) are best for unit tests where speed is king.
  • Network interception (MSW/Nock) provides a more realistic environment without implementation coupling.
  • Mock servers (WireMock/Prism) are necessary for E2E and integration tests.
  • Avoid Mock Drift by using contract testing or generating mocks from API specifications.
  • Always test the Sad Path. Your application's resilience is defined by how it handles API failures, not just its successes.

Build Your Next Product with Increments Inc.

Testing is just one piece of the puzzle. Building a world-class digital product requires a partner who understands the intersection of high-level business strategy and deep technical excellence.

At Increments Inc., we don't just write code; we build scalable solutions. Whether you are a startup looking for an MVP or an enterprise needing platform modernization, our team in Dhaka and Dubai is ready to help.

Ready to get started?
When you inquire about a project, we provide:

  1. A Free AI-Powered SRS Document: A comprehensive, IEEE 830 standard requirement specification to kickstart your project.
  2. A $5,000 Technical Audit: We’ll dive into your existing codebase or architecture plan to find optimizations—no strings attached.

Start Your Project Today or reach out to us on WhatsApp to chat with our engineering lead.

Let’s build something incredible together.

Topics

API TestingMockingSoftware ArchitectureDevOpsUnit TestingIntegration Testing

Written by

II

Increments Inc.

Engineering Team

Want to build something?

Get a free consultation and technical audit worth $5,000. We'll help you build your next successful product.

  • Free $5,000 technical audit
  • No upfront payment required
  • 14+ years of experience