Lesson 4 · Pytest deep dive
pytest Tutorial #4: Mocking & Monkeypatch
On testing real Python code with pytest. unittest.mock basics and the built-in monkeypatch fixture. Let's get into it. Mock objects are fake, controllable…
- CoursePytest deep dive
- Lesson4 of 18
- FormatJupyter notebook · 8 code cells
What you'll learn
Data
No separate download needed — the notebook creates or downloads everything it uses.
📓 Full notebook
Download .ipynbpytest Deep-Dive, Video 4: Mocking and monkeypatch#
- Video four of an eighteen-part series on testing real Python code with pytest.
- unittest.mock basics and the built-in monkeypatch fixture.
- Let's get into it.
Part 1: The Helper From Last Time#
import pytest, tempfile, os
def run_pytest(code_str, args=None):
with tempfile.NamedTemporaryFile(mode='w', suffix='_test.py', delete=False) as f:
f.write(code_str)
path = f.name
result = pytest.main([path, *(args or [])])
os.unlink(path)
return result
Part 2: Why Mock - the External-Dependency Problem#
from unittest.mock import Mock
fake_response = Mock()
fake_response.status_code = 200
fake_response.json.return_value = {'ok': True}
print(fake_response.status_code)
print(fake_response.json())
Part 3: Mock Call Tracking#
code = '''
from unittest.mock import Mock
def send_notification(sender, message):
sender.send(message)
def test_send_notification_calls_sender():
fake_sender = Mock()
send_notification(fake_sender, 'hello')
assert fake_sender.send.called
fake_sender.send.assert_called_once_with('hello')
'''
exit_code = run_pytest(code, ['-v', '--no-header'])
print(exit_code)
Part 4: monkeypatch - Replacing a Function#
code = '''
import random
def roll_die():
return random.randint(1, 6)
def test_roll_die_is_patched(monkeypatch):
monkeypatch.setattr(random, 'randint', lambda a, b: 4)
assert roll_die() == 4
'''
exit_code = run_pytest(code, ['-v', '--no-header'])
print(exit_code)
Part 5: monkeypatch Automatically Undoes Itself#
code = '''
import random
def roll_die():
return random.randint(1, 6)
def test_patched_first(monkeypatch):
monkeypatch.setattr(random, 'randint', lambda a, b: 4)
assert roll_die() == 4
def test_unpatched_after(monkeypatch):
result = roll_die()
assert 1 <= result <= 6
'''
exit_code = run_pytest(code, ['-v', '--no-header'])
print(exit_code)
Part 6: monkeypatch and Environment Variables#
code = '''
import os
def get_api_url():
return os.environ.get('API_URL', 'https://default.example.com')
def test_get_api_url_uses_env(monkeypatch):
monkeypatch.setenv('API_URL', 'https://staging.example.com')
assert get_api_url() == 'https://staging.example.com'
def test_get_api_url_falls_back(monkeypatch):
monkeypatch.delenv('API_URL', raising=False)
assert get_api_url() == 'https://default.example.com'
'''
exit_code = run_pytest(code, ['-v', '--no-header'])
print(exit_code)
Part 7: unittest.mock.patch as a Context Manager#
code = '''
from unittest.mock import patch
import datetime
def get_year():
return datetime.date.today().year
def test_get_year_patched():
fixed_date = datetime.date(2030, 1, 1)
with patch('datetime.date') as mock_date:
mock_date.today.return_value = fixed_date
assert get_year() == 2030
'''
exit_code = run_pytest(code, ['-v', '--no-header'])
print(exit_code)
Part 8: A Real Pattern - Testing Code That Calls a Network Function#
code = '''
def fetch_weather(city):
raise RuntimeError("real network call, should never run in tests")
def describe_weather(city, fetch=fetch_weather):
data = fetch(city)
return f"{city}: {data['condition']}, {data['temp_c']} C"
def test_describe_weather_with_fake_fetch():
def fake_fetch(city):
return {'condition': 'sunny', 'temp_c': 22}
result = describe_weather('Perth', fetch=fake_fetch)
assert result == 'Perth: sunny, 22 C'
'''
exit_code = run_pytest(code, ['-v', '--no-header'])
print(exit_code)
Wrap-Up: What You Learned#
- Mock objects are fake, controllable stand-ins for real dependencies like network calls or external services.
- Every Mock records how it was called; called, call_count, and assert_called_with verify real interactions.
- monkeypatch is a built-in pytest fixture requiring no import, used like any other fixture parameter.
- monkeypatch.setattr replaces an attribute or function; changes automatically revert after each test.
- monkeypatch.setenv and delenv safely control environment variables for the duration of one test.
- unittest.mock.patch offers a context-manager alternative, scoped to a with block.
- Injecting a dependency as a default parameter makes it trivial to swap in a fake for fast, offline tests.
- That wraps up mocking and monkeypatch. Next up: Markers.
Found this useful?
All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.



