Mocking external services and load testing with Locust are important practices for building reliable and scalable applications. Mocking external services involves simulating third-party APIs or dependencies during development and testing so that application behavior can be verified without relying on real external systems.
Locust is a load testing tool that helps measure how an application performs under different levels of user traffic by simulating concurrent users and real-world usage patterns.
Mocking External Services
Mocking replaces real external dependencies with controlled simulations, speeding up tests and eliminating variables like internet connectivity. This approach is essential for integration testing and CI/CD pipelines, following industry standards like those in pytest and unittest.mock from Python's standard library.
Why Mock External Services?
Real external services introduce unpredictability—downtime, slow responses, or changing APIs can break your tests. Mocking ensures deterministic testing, where outcomes depend only on your code.

For example, imagine your e-commerce API calls Stripe for payments. Mocking lets you simulate success, declines, or timeouts without real charges.
Core Mocking Techniques in Python
Python's unittest.mock library (built-in since Python 3.3) is the gold standard for object-level mocking, while responses and httpx-mock excel for HTTP clients. Choose based on your framework: FastAPI pairs well with pytest-httpx, Django with responses.

Step-by-Step: Mocking an HTTP Call with responses
Consider a FastAPI service fetching user data from a third-party auth API. We'll mock it for testing.
1. Install dependencies: pip install responses pytest.
2. Define your service function
import requests
from typing import Dict
def get_user_profile(user_id: str) -> Dict:
response = requests.get(f"https://api.external.com/users/{user_id}")
response.raise_for_status()
return response.json()3. Write a test with mocking
import responses
import pytest
@responses.activate
def test_get_user_profile():
responses.add(
responses.GET,
"https://api.external.com/users/123",
json={"id": 123, "name": "Alice", "email": "alice@example.com"},
status=200
)
profile = get_user_profile("123")
assert profile["name"] == "Alice"4. Run with pytest: It passes without calling the real API.
This technique scales to async code using pytest-httpx for httpx clients in FastAPI.
Advanced Mocking: Handling Failures and Retries
Simulate real-world issues to test resilience, like using tenacity for retries.
1. Mock delays: responses.add(..., body="...", status=503, headers={"Retry-After": "5"}).
2. Chain responses: Add sequential mocks for retry logic.
3. Dynamic mocks: Use callbacks for request-specific responses.
Pro Tip: In production CI, combine with Dockerized mocks (e.g., WireMock) for team consistency.
Load Testing Your API with Locust
Once your API is mocked and unit-tested, load testing validates performance under stress. Locust is an open-source Python tool (latest v2.20+ as of 2026) that lets you write concise user behavior scripts, scaling to millions of simulated users via gevent.
Understanding Locust and Its Advantages
Unlike JMeter's GUI, Locust uses pure Python code for tests, making it developer-friendly and version-controllable. It reports metrics like requests/sec, failure rates, and response times in real-time web UI.
Key benefits include
1. Scriptable: Define user actions as Python classes.
2. Distributed: Run across machines for massive scale.
3. Extensible: Plugins for AWS, Grafana integration.
Locust follows best practices from tools like Artillery, emphasizing realistic user simulations.
Setting Up Locust for Your Web API
1. Install: pip install locust[ui].
2. Create locustfile.py in your project root.
3. Basic structure
from locust import HttpUser, task, between
class ApiUser(HttpUser):
wait_time = between(1, 3) # Wait 1-3s between tasks
@task
def create_user(self):
self.client.post("/users/", json={"name": "Test User", "email": "test@example.com"})
@task(2) # Weight: twice as frequent
def get_users(self):
self.client.get("/users/")4. Run: locust -f locustfile.py --host=http://localhost:8000.
5. Open http://localhost:8089, spawn users, and watch metrics.
Defining Realistic Load Scenarios
Craft tasks mimicking full-stack flows, like login → create resource → read → delete.
Common Patterns

Example: Stress Testing with Ramp-up
locust -f locustfile.py --host=http://localhost:8000 --users=1000 --spawn-rate=10 --run-time=5m --html=report.htmlThis spawns 1000 users over 5 minutes, generating an HTML report.
Analyzing Results and Best Practices
Locust's dashboard shows RPS (requests per second), 95th percentile response time, and failure buckets. Aim for <200ms P95 under load.
1. Thresholds: Alert if >5% failures or P95 >500ms.
2. Scaling Tips: Use --master/--worker for distributed runs; integrate with Prometheus.
3. Common Pitfalls: Avoid testing from localhost (use --host); mock databases for pure API load.
In a real project, combine with New Relic or Datadog for deeper insights.
Integrating Mocking and Load Testing in CI/CD
Tie these together in GitHub Actions or Jenkins for automated validation.
1. Mock externals in unit tests.
2. Run Locust in parallel CI jobs.
3. Fail builds on performance regressions.
Sample GitHub Workflow Snippet
- name: Load Test
run: locust -f locustfile.py --headless --users=500 --spawn-rate=50 --run-time=2m --html=report.html --failure-percentage=1This ensures every PR maintains API health.