Challenge Goals
3 min- Mock time so "now" is deterministic.
- Mock randomness so dice are predictable.
- Mock the filesystem (or use tmp_path) so tests leave no trace.
- Mock a chain of dependent API calls.
Warm-Up · The Four Events
5 minEvent 1 — TIME: a function that behaves differently by hour/date Event 2 — RANDOM: a function whose output depends on random Event 3 — FILESYSTEM: a function that reads/writes files Event 4 — API CHAIN: a function that calls API A, then API B with A's result
Every "I can't test this" situation has a mocking pattern. By the end of these four events you'll have a technique for the clock, the dice, the disk, and dependent network calls — covering 90% of real-world "hard to test" code.
The Techniques Recap
14 minEvent 1 — mock the clock
# greet.py from datetime import datetime def greeting(): h = datetime.now().hour return "Good morning" if h < 12 else "Good evening"
from unittest.mock import patch @patch("greet.datetime") def test_morning(mock_dt): mock_dt.now.return_value.hour = 9 assert greeting() == "Good morning"
Event 2 — mock randomness
# dice.py import random def roll_two(): return random.randint(1, 6) + random.randint(1, 6)
@patch("dice.random.randint") def test_roll(mock_rand): mock_rand.side_effect = [3, 4] # first call 3, second 4 assert roll_two() == 7
Event 3 — mock the filesystem (or use tmp_path)
# tmp_path is usually cleaner than mocking open(): def test_write(tmp_path): path = tmp_path / "out.txt" save_data(path, "hello") assert path.read_text() == "hello" # but if you must mock open(): from unittest.mock import patch, mock_open @patch("builtins.open", new_callable=mock_open, read_data="cached") def test_read(m): assert load_cache() == "cached"
Event 4 — mock a chain
# flow.py import requests def user_repos(username): user = requests.get(f"/users/{username}").json() repos = requests.get(user["repos_url"]).json() return [r["name"] for r in repos]
@patch("flow.requests.get") def test_chain(mock_get): # return different responses on each call mock_get.side_effect = [ Mock(json=Mock(return_value={"repos_url": "/u/1/repos"})), Mock(json=Mock(return_value=[{"name": "alpha"}, {"name": "beta"}])), ] assert user_repos("aisyah") == ["alpha", "beta"] assert mock_get.call_count == 2
Worked Example · Event 4 in Full
12 min# test_flow.py — mocking a two-step API chain from unittest.mock import patch, Mock from flow import user_repos @patch("flow.requests.get") def test_user_repos_chain(mock_get): # call 1 → the user object; call 2 → their repos user_resp = Mock() user_resp.json.return_value = {"repos_url": "https://api/u/1/repos"} repos_resp = Mock() repos_resp.json.return_value = [{"name": "alpha"}, {"name": "beta"}] mock_get.side_effect = [user_resp, repos_resp] result = user_repos("aisyah") assert result == ["alpha", "beta"] assert mock_get.call_count == 2 # verify the SECOND call used the url from the FIRST response second_call_url = mock_get.call_args_list[1].args[0] assert second_call_url == "https://api/u/1/repos"
$ pytest test_flow.py -v test_user_repos_chain PASSED 1 passed
Read the diff
The key is side_effect = [resp1, resp2] — the mock returns a different response on each call, scripting the two-step flow. Then call_args_list[1] lets us verify the second request used the URL extracted from the first response — proving the chain wired up correctly, all without a network. That's the hardest mocking pattern, mastered.
Compete in All Four
13 minWrite and test a function that picks a random greeting depending on the time of day. Mock BOTH the clock and random for a fully deterministic test.
Test a function that appends a line to a log file. Use tmp_path so nothing is left behind. Then try the same with mock_open.
Build and test a function that fetches a city's coordinates from one endpoint, then its weather from another (the Level-4 weather pattern). Mock both calls in sequence.
Gold Medal · A Fully-Mocked Pipeline
8 minWrite a function that: reads a config file, calls an API with a key from it, stamps the result with the current time, and writes a report. Then write ONE test that mocks all four dependencies (file, API, clock, output file) so it runs offline, deterministically, leaving no trace. The ultimate mocking decathlon.
Recap
3 minFour patterns: patch datetime for the clock, patch random (or use side_effect lists) for dice, use tmp_path or mock_open for files, and side_effect = [resp1, resp2] for API chains. With these you can test almost any code, however entangled with the outside world. Next: measuring how much of your code the tests actually run — coverage.
Vocabulary Card
- side_effect list
- Make a mock return different values on successive calls.
- call_args_list
- The record of every call's arguments — verify a multi-call sequence.
- mock_open
- A helper to mock the built-in
open()with fake file contents. - deterministic test
- A test whose result never depends on time, randomness, or the network.
Homework
4 minComplete the gold-medal pipeline: a function with four external dependencies and one test that mocks them all. Confirm the test runs offline, gives the same result every time, and leaves no files behind. Write a one-line note per dependency on which technique you used.
Combine: tmp_path (files), @patch("mod.requests.get") (API), @patch("mod.datetime") (clock). Stack the patches; remember the bottom-up argument order.