Learning Goals
3 min- Patch
requests.getto return a fake response. - Mock
.json(),.status_code, andraise_for_status. - Test error paths: timeouts, 404s, bad JSON.
- Meet the
responseslibrary for cleaner HTTP mocks.
Warm-Up · The Code to Test
5 min# weather.py import requests def get_temp(city): resp = requests.get(f"https://api.example.com/weather/{city}", timeout=5) resp.raise_for_status() return resp.json()["temp"]
To test get_temp we fake the response object that requests.get returns — giving it a .json() method and a .status_code. Then we test our logic (extracting temp, handling errors) without any network.
New Concept · Faking Responses
14 minPatch requests.get, return a fake response
from unittest.mock import patch, Mock from weather import get_temp @patch("weather.requests.get") # patch where it's USED def test_get_temp(mock_get): fake = Mock() fake.json.return_value = {"temp": 31} fake.raise_for_status.return_value = None # no error mock_get.return_value = fake assert get_temp("KL") == 31 mock_get.assert_called_once_with( "https://api.example.com/weather/KL", timeout=5)
Simulate an HTTP error (404)
import requests, pytest from unittest.mock import patch, Mock @patch("weather.requests.get") def test_404_raises(mock_get): fake = Mock() fake.raise_for_status.side_effect = requests.HTTPError("404") mock_get.return_value = fake with pytest.raises(requests.HTTPError): get_temp("Atlantis")
Simulate a network timeout
@patch("weather.requests.get") def test_timeout(mock_get): mock_get.side_effect = requests.Timeout("too slow") with pytest.raises(requests.Timeout): get_temp("KL")
Simulate bad JSON
@patch("weather.requests.get") def test_bad_json(mock_get): fake = Mock() fake.json.side_effect = ValueError("not json") mock_get.return_value = fake with pytest.raises(ValueError): get_temp("KL")
Cleaner: the responses library
# pip install responses import responses, requests @responses.activate def test_with_responses(): responses.add(responses.GET, "https://api.example.com/weather/KL", json={"temp": 31}, status=200) assert get_temp("KL") == 31
responses registers fake endpoints by URL — no Mock plumbing. It's the cleaner choice for code that makes many HTTP calls. (For httpx there's respx; same idea.)
Worked Example · Full Offline Suite
12 min# test_weather.py — every path, zero network import requests, pytest from unittest.mock import patch, Mock from weather import get_temp def _fake_response(json_data=None, error=None): fake = Mock() fake.raise_for_status.return_value = None if error: fake.raise_for_status.side_effect = error fake.json.return_value = json_data return fake @patch("weather.requests.get") def test_success(mock_get): mock_get.return_value = _fake_response({"temp": 28}) assert get_temp("Penang") == 28 @patch("weather.requests.get") def test_http_error(mock_get): mock_get.return_value = _fake_response(error=requests.HTTPError("500")) with pytest.raises(requests.HTTPError): get_temp("X") @patch("weather.requests.get") def test_timeout(mock_get): mock_get.side_effect = requests.Timeout() with pytest.raises(requests.Timeout): get_temp("X") @patch("weather.requests.get") def test_url_and_timeout_used(mock_get): mock_get.return_value = _fake_response({"temp": 30}) get_temp("JB") mock_get.assert_called_once_with( "https://api.example.com/weather/JB", timeout=5)
$ pytest test_weather.py -v test_success PASSED test_http_error PASSED test_timeout PASSED test_url_and_timeout_used PASSED 4 passed in 0.02s ← no internet needed
Read the diff
A helper _fake_response builds the mock response so each test stays short. We cover the happy path, an HTTP error, a timeout, and verify the URL + timeout were used correctly — all offline, instantly, deterministically. This is exactly how you'd test the Level-4 weather and trivia apps reliably.
Try It Yourself
13 minPatch requests.get for an API function of yours; return fake JSON; assert your code extracts the right field.
Add tests for a timeout and a 404, asserting your code raises (or handles) each. Use side_effect.
Rewrite one of your HTTP tests using the responses library instead of manual Mock plumbing. Compare clarity.
Mini-Challenge · Test the Trivia Fetcher
8 minTake the Level-4 trivia fetch_questions (calls Open Trivia DB). Mock the HTTP call to return a canned 2-question payload. Test: it returns 2 questions, it decodes HTML entities, and it raises on a non-zero response_code.
Show the structure
from unittest.mock import patch, Mock FAKE = {"response_code": 0, "results": [ {"question": "What is "HTTP"?", "correct_answer": "...", "incorrect_answers": ["a", "b", "c"]}, {"question": "Q2", "correct_answer": "x", "incorrect_answers": ["y","z","w"]}, ]} @patch("trivia.requests.get") def test_fetch_two(mock_get): mock_get.return_value = Mock( raise_for_status=Mock(), json=Mock(return_value=FAKE)) qs = fetch_questions(2) assert len(qs) == 2 @patch("trivia.requests.get") def test_bad_response_code(mock_get): bad = {"response_code": 1, "results": []} mock_get.return_value = Mock(raise_for_status=Mock(), json=Mock(return_value=bad)) import pytest with pytest.raises(SystemExit): fetch_questions(2)
Non-negotiables: canned payload, test count + the response_code error path, all offline.
Recap
3 minPatch requests.get (where it's used) and return a fake response with .json() / .status_code / raise_for_status. Use side_effect for timeouts and HTTP errors. The responses library makes URL-based HTTP mocking cleaner. Now your Level-4 API apps are fully testable offline. Next: a mocking challenge.
Vocabulary Card
- fake response
- A Mock standing in for a requests Response (with json/status/raise_for_status).
- side_effect = Timeout
- Simulate a network error by raising from the patched call.
- responses library
- A tool to register fake HTTP endpoints by URL for clean mocking.
- offline test
- A test that never touches the real network — fast and reliable.
Homework
4 minPick one of your Level-4 API apps (weather, trivia, GitHub). Write a fully offline test suite covering: a successful response, a timeout, an HTTP error, and a verification that the correct URL/params were used. Bonus: redo one test with the responses library.
Model on test_weather.py with the _fake_response helper. Patch the requests.get reference in YOUR module's namespace.