pytest fixtures + mocking
A fixture is a factory function pytest injects into any test that names it as an argument.
Why they matter
Without fixtures, every test starts with 5 lines of setup. With fixtures:
- Setup lives in one place.
- Each test gets a fresh instance (default scope=
"function"). - Fixtures can depend on other fixtures — composition for free.
Scopes
function(default) — new per test.class— shared across tests in one class.module— shared across a file.session— created once. Use for expensive stuff (real DB, browser).
Mocking with monkeypatch
The best mocking tool comes built into pytest:
def test_something(monkeypatch):
monkeypatch.setattr(mymodule, "send_email", fake_send)
# ... test uses fake_send until test ends, then auto-restored
Cleaner than unittest.mock.patch for most cases. Auto-undoes at test teardown.
unittest.mock when you need it
For call-tracking, MagicMock is unbeatable:
from unittest.mock import MagicMock
mock_client = MagicMock()
mock_client.fetch.return_value = {"ok": True}
service = MyService(mock_client)
service.do_thing()
mock_client.fetch.assert_called_once_with("some-arg")
Try it
- Add a
bobfixture that depends onrepo. - Write a test that monkeypatches
send_welcome_emailto raise, and assertssignupstill returns a valid uid (or handles the error — your choice, document it).
3 graded exercises
Write the code, click Check. We’ll tell you exactly what to fix.
- Exercise 1
Define a pytest fixture
alicethat returns{'name': 'Alice', 'age': 30}. Then definetest_alice(alice)that assertsalice['name'] == 'Alice'. Since Pyodide can't run the pytest runner, calltest_alicemanually by passing the dict directly, then print'ok'. - Exercise 2
Fixture composition. Define a fixture
reporeturning an emptydict(). Then definealice_repo(repo)that adds{'alice': 30}to the passed repo and returns it. Manually chain:_r = repo(); result = alice_repo(_r); print the result. - Exercise 3
Use
pytest.MonkeyPatch()directly (since we can't get the auto-injected fixture in Pyodide). Defineread_env()returningos.environ.get('API_KEY', 'unset'). Createmp = pytest.MonkeyPatch(), callmp.setenv('API_KEY', 'secret123'), printread_env(). Thenmp.undo()and printread_env()again. Expected output:secret123thenunset.