Mocking and Patching
Replace external boundaries with test doubles, verify interactions, and avoid mocks that hide real integration failures.
By the end, you’ll be able to…
- Choose between a fake and a mock.
- Configure return values and side effects.
- Patch the name where it is used, not where it is defined.
- Decide when a mock adds more risk than value.
01Test doubles replace a boundary
A test double stands in for a dependency so the unit under test can run quickly and deterministically. A fake is a small working implementation, such as an in-memory repository. A mock is a recording object that lets you assert how it was called. Prefer the simplest double that answers the question.
A fake is usually easier to read when the collaborator has behavior. A mock is useful when the interaction itself is the contract: a payment charge, an email send, or a call to an external API. The choice is about what you need to verify, not about which tool is more advanced.
02Mocks record interactions
unittest.mock.Mock creates an object that accepts any attribute access and records calls. Set return_value for a predictable result, or side_effect when the call should raise or return a sequence. Assertions such as assert_called_once_with verify the exact interaction the caller promised.
Use autospec=True when patching a real callable so the mock rejects arguments the real function would reject. A free-form Mock can accept a typo and let a broken call pass the test. The stricter double catches more, earlier.
03Patch where the name is used
patch temporarily replaces a name for the duration of a test. The common mistake is patching the definition module instead of the module that imported the name. If billing.py does from payments import charge, patch billing.charge, because that is the name the code under test actually looks up.
Use patch as a context manager or decorator so the original is restored even when the test fails. Prefer patching a small, explicit boundary over patching a large module. When a dependency can be injected, injection is usually clearer than patching a global name.
04Mock less than you think
Over-mocking produces tests that verify the mock choreography rather than the application. If every collaborator is mocked, the test can pass while the real integration is broken. Keep a small number of contract tests that use a realistic fake or a temporary local service.
A mock should answer a specific question: was the charge attempted once with the right amount, or did the code handle a timeout? Write that question in the test name. If the test needs five mocks to set up, the unit probably has too many responsibilities.
The ideas worth keeping.
- Use a fake for behavior and a mock for an interaction contract.
- Configure return values and side effects deliberately.
- Patch the lookup site and keep a few realistic contract tests.
Make it yours.
Type and test your code right here in the interactive workspace. Run your solution, check the output, and compare with the worked guide when ready.
Mock a payment gateway
Test a checkout function that charges a gateway exactly once with the correct amount. Configure the mock to return 'approved' and assert both the result and the call.
Explore a worked solution
from unittest.mock import Mockdef checkout(gateway, amount): return gateway.charge(amount)gateway = Mock()gateway.charge.return_value = "approved"assert checkout(gateway, 2500) == "approved"gateway.charge.assert_called_once_with(2500)print("gateway contract verified")The mock is the boundary. The test checks the observable result and the interaction that matters. It does not pretend to verify the real payment provider's behavior.
Patch time for an expiry rule
Write is_expired(expires_on) that compares a date to date.today(). Patch date.today to a fixed value and test both an expired and a current date.
Explore a worked solution
from datetime import datefrom unittest.mock import patchdef is_expired(expires_on): return expires_on < date.today()with patch("datetime.date") as fake_date: fake_date.today.return_value = date(2026, 1, 10) assert is_expired(date(2026, 1, 1)) assert not is_expired(date(2026, 1, 10))print("expiry boundary verified")Patching today removes the live clock from the test. The boundary date itself is not expired, which is the off-by-one case that a naive < versus <= test often misses.
What stayed with you?
Check the ideas while they’re fresh. Your answers stay in this page and feedback appears as you go.
Mock a payment gateway
Test a checkout function that charges a gateway exactly once with the correct amount. Configure the mock to return 'approved' and assert both the result and the call.
gateway contract verified
Keep exploring.
The official references behind this lesson, when you want a second angle or the full details.
One lesson closer.
Reading is a start. Trying it yourself makes the connection.