Scenario: Mocking an API Call in Pytest
Let’s assume we have a function get_weather_data()
that fetches weather details from an external API using the requests
library. Since requests.get()
makes an actual network call, we need to mock it in our tests.
# The function to be tested
import requests
def get_weather_data(city):
"""Fetches weather data for a given city from an external API."""
url = f"https://api.weather.com/v3/weather/{city}"
response = requests.get(url)
if response.status_code == 200:
return response.json()
else:
return {"error": "Unable to fetch data"}
Using Pytest’s monkeypatch
to Mock API Calls
import pytest
def mock_requests_get(*args, **kwargs):
"""Mock function to replace requests.get"""
class MockResponse:
def __init__(self, json_data, status_code):
self.json_data = json_data
self.status_code = status_code
def json(self):
return self.json_data
# Simulate a successful response
if "weather" in args[0]:
return MockResponse({"temperature": 25, "condition": "Sunny"}, 200)
return MockResponse({"error": "Invalid request"}, 400)
def test_get_weather_data(monkeypatch):
"""Test function using monkeypatch to mock requests.get"""
monkeypatch.setattr(requests, "get", mock_requests_get)
response = get_weather_data("NewYork")
assert response == {"temperature": 25, "condition": "Sunny"}
Breakdown:
- We define
mock_requests_get()
to return a fake response instead of making an actual API call. - We use
monkeypatch.setattr(requests, "get", mock_requests_get)
to overriderequests.get
with our mock function. - The test then verifies that the function correctly processes the mocked response.
Using unittest.mock
for More Flexibility
The unittest.mock
library provides a more powerful way to mock external dependencies.
Key Advantages:
patch("requests.get")
dynamically replacesrequests.get
with a mock.mock_get.return_value
allows us to control the return values without defining a separate mock function.- The approach is reusable and works well with more complex interactions.
from unittest.mock import patch
import requests
@patch("requests.get")
def test_get_weather_data_with_mock(mock_get):
"""Test function using unittest.mock to patch requests.get"""
mock_get.return_value.status_code = 200
mock_get.return_value.json.return_value = {"temperature": 30, "condition": "Cloudy"}
response = get_weather_data("London")
assert response == {"temperature": 30, "condition": "Cloudy"}
Handling Edge Cases in Mocking
Simulating API Failures
@patch("requests.get")
def test_get_weather_data_failure(mock_get):
mock_get.return_value.status_code = 500
mock_get.return_value.json.return_value = {"error": "Server Error"}
response = get_weather_data("Paris")
assert response == {"error": "Unable to fetch data"}
Simulating Timeouts
@patch("requests.get")
def test_get_weather_data_timeout(mock_get):
mock_get.side_effect = requests.exceptions.Timeout
with pytest.raises(requests.exceptions.Timeout):
get_weather_data("Berlin")
Simulating Different Response Structures
# APIs often return different data structures under different conditions. Mocking lets you test all possible variations.
@patch("requests.get")
def test_get_weather_data_invalid_json(mock_get):
mock_get.return_value.status_code = 200
mock_get.return_value.json.side_effect = ValueError("Invalid JSON")
response = get_weather_data("Tokyo")
assert response == {"error": "Unable to fetch data"}
Best Practices for Mocking in Pytest
✅ Keep Mocks as Close to Reality as Possible – Ensure that mock responses resemble real API responses.
✅ Test Both Success & Failure Cases – Always test edge cases, including timeouts, incorrect data, and server errors.
✅ Use unittest.mock.patch
for Cleaner Code – It’s more concise and integrates well with pytest
.
✅ Avoid Over-Mocking – Mocking too much can lead to tests that don’t truly reflect real-world behavior.
Advanced Mocking Techniques
1️⃣ Using responses
Library for HTTP Mocking
# Instead of manually mocking requests.get, you can use the responses library to mock entire HTTP requests.
import responses
@responses.activate
def test_get_weather_data_with_responses():
responses.add(
responses.GET,
"https://api.weather.com/v3/weather/NewYork",
json={"temperature": 20, "condition": "Windy"},
status=200
)
response = get_weather_data("NewYork")
assert response == {"temperature": 20, "condition": "Windy"}
2️⃣ Using pytest-mock
for Cleaner Mocking
# The pytest-mock plugin simplifies mocking in Pytest.
def test_get_weather_data_with_pytest_mock(mocker):
mock_get = mocker.patch("requests.get")
mock_get.return_value.status_code = 200
mock_get.return_value.json.return_value = {"temperature": 28, "condition": "Rainy"}
response = get_weather_data("Mumbai")
assert response == {"temperature": 28, "condition": "Rainy"}
📚 Further Reading & Resources
🔗 Popular Resources
🎯 Underrated Resources
📌 Popular GitHub Repositories
🤖 AI Tools for Improved Testing
- CodiumAI – AI-powered test case generation
- ChatGPT API – Generate automated test mocks
- DeepCode – AI-driven static code analysis