Mocking in tests using unittest.mock
Fast tests cannot always call real SMTP servers, payment gateways, or petabyte databases. unittest.mock ships with Python and lets you replace callables or objects with fakes that record interactions while returning canned data.
📚 Prerequisites
- Pytest or unittest comfort from earlier articles in this series.
🎯 What you'll learn
- Patch functions at import paths with
@patch. - Assert call counts and arguments via
Mockobjects. - Avoid over-mocking (testing mocks instead of your code).
Patch a function
from unittest.mock import patch
import mypackage.notifications as notifications
@patch("mypackage.notifications.send_email")
def test_signup_sends_mail(mock_send):
notifications.handle_signup("[email protected]")
mock_send.assert_called_once()
Patch where the symbol is looked up, not necessarily its definition module—this trips newcomers.
Spy on methods
from unittest.mock import MagicMock
fake_db = MagicMock()
fake_db.fetch_user.return_value = {"id": 1}
assert fake_db.fetch_user(1)["id"] == 1
fake_db.fetch_user.assert_called_with(1)
When not to mock
- Heavy reliance on mocks for your own internal functions masks integration bugs—pair with a few real component tests.
- Prefer fakes/in-memory databases when setup cost is tolerable; they behave closer to production.
💡 Key takeaways
- Mocks document expected collaboration between modules—treat
assert_called_*as part of the spec.
➡️ Next steps
Quantify blind spots with code coverage.