Integration testing ensures that different parts of an application work together as expected. When an application interacts with external services, real API calls can slow down tests, introduce dependencies, and make the testing process unreliable. To mitigate these issues while keeping tests close to real-world scenarios, API stubbing is used.
In this blog, weβll cover:
- What API stubbing is and why itβs useful
- How it differs from mocking
- Popular techniques for API stubbing in Pytest
- Tools like responses, httpretty, and WireMock
- Best practices for integration testing with stubbing
- Useful resources, GitHub repositories, and AI tools
π What is API Stubbing?
API stubbing is the process of replacing real API calls with predefined responses in integration tests. Unlike unit test mocks, stubs provide more realistic behavior and are often used when testing a fully integrated application instead of individual functions.
π Mocking vs. Stubbing
Feature | Mocking (Unit Test) | Stubbing (Integration Test) |
---|---|---|
Purpose | Isolate unit tests | Simulate real API behavior |
Scope | Function level | System or module level |
Realism | Returns predefined results | Mimics API responses more closely |
Dependency | No dependency on external service | May require API contract compliance |
Tools | unittest.mock , pytest-mock |
responses , httpretty , WireMock , MockServer |
Β
β
Using responses
for API Stubbing in Pytest
1οΈβ£ The Function to be Tested
Letβs assume we have a function that fetches user details from an external API.
import requests
def get_user_info(user_id):
"""Fetches user data from an external service."""
url = f"https://api.example.com/users/{user_id}"
response = requests.get(url)
if response.status_code == 200:
return response.json()
else:
return {"error": "User not found"}
2οΈβ£ Stubbing the API Using responses
The responses
library allows you to intercept HTTP requests and return predefined responses.
πΉ How It Works
@responses.activate
enables request interception.responses.add()
defines a stubbed API response.- The function under test makes an HTTP request, but
responses
intercepts it and returns the predefined response.
# pip install responses
import responses
import pytest
from mymodule import get_user_info # Import the function under test
@responses.activate
def test_get_user_info():
"""Integration test with API stubbing using responses."""
# Define the stubbed response
responses.add(
responses.GET,
"https://api.example.com/users/123",
json={"id": 123, "name": "John Doe", "email": "john@example.com"},
status=200
)
# Call the function and verify the response
response = get_user_info(123)
assert response == {"id": 123, "name": "John Doe", "email": "john@example.com"}
π₯ Handling Edge Cases in Stubbing
1οΈβ£ Simulating an API Failure (500 Error)
@responses.activate
def test_get_user_info_api_failure():
responses.add(
responses.GET,
"https://api.example.com/users/123",
json={"error": "Internal Server Error"},
status=500
)
response = get_user_info(123)
assert response == {"error": "User not found"}
2οΈβ£ Simulating a Timeout
import requests
@responses.activate
def test_get_user_info_timeout():
responses.add(
responses.GET,
"https://api.example.com/users/123",
body=requests.exceptions.Timeout()
)
with pytest.raises(requests.exceptions.Timeout):
get_user_info(123)
3οΈβ£ Simulating API Rate Limiting (429 Error)
@responses.activate
def test_get_user_info_rate_limit():
responses.add(
responses.GET,
"https://api.example.com/users/123",
json={"error": "Too many requests"},
status=429
)
response = get_user_info(123)
assert response == {"error": "User not found"}
π― Using httpretty
for API Stubbing
Another popular library for API stubbing is httpretty
, which works similarly to responses
but offers more flexibility.
πΉ httpretty
Advantages
- Supports dynamic response modification (useful for real-time testing).
- Can mock both HTTP and HTTPS requests.
- Works well with third-party HTTP clients like
urllib3
.
# pip install httpretty
import httpretty
import pytest
from mymodule import get_user_info
@httpretty.activate
def test_get_user_info_with_httpretty():
"""Integration test with API stubbing using httpretty."""
httpretty.register_uri(
httpretty.GET,
"https://api.example.com/users/123",
body='{"id": 123, "name": "Alice", "email": "alice@example.com"}',
content_type="application/json"
)
response = get_user_info(123)
assert response == {"id": 123, "name": "Alice", "email": "alice@example.com"}
π Using WireMock
for Advanced Stubbing
For more complex API stubbing, especially when testing microservices, WireMock (a standalone API mocking tool) is useful.
Setting Up WireMock
-
Install WireMock in Docker
-
Define an API Stub in WireMock
-
Run the integration test by making a request to
http://localhost:8080/users/123
instead of the real API.
πΉ WireMock Benefits
β
Supports stateful API stubbing.
β
Allows recording & replaying real API interactions.
β
Can be used locally or in CI/CD pipelines.
# docker run -d -p 8080:8080 wiremock/wiremock
curl -X POST http://localhost:8080/__admin/mappings -H "Content-Type: application/json" -d '{
"request": {
"method": "GET",
"url": "/users/123"
},
"response": {
"status": 200,
"body": "{\"id\": 123, \"name\": \"Bob\"}"
}
}'
π Best Practices for API Stubbing in Pytest
β
Use responses
for lightweight stubbing β Ideal for simple tests.
β
Use httpretty
for compatibility with multiple HTTP clients.
β
Use WireMock
for more complex API interactions.
β
Ensure stubbed responses match real API contracts β This avoids unexpected issues in production.
β
Test failure scenarios β Always simulate timeouts, rate limits, and server failures.
π Further Reading & Resources
π Popular Resources
π― Underrated Resources
π Popular GitHub Repositories
π€ AI Tools for Improved Testing
- Mockoon β No-code API mocking
- Postman Mock Server β API stubbing & contract validation