Tests must not call real payment APIs or wait on networks, and mocks let you test the logic around them.
Mocking replaces a dependency with a stand-in you control. pytest works with the standard library's unittest.mock:
Mock()/MagicMock()accept any call or attribute. Setreturn_valueorside_effect(an exception to raise, or a function or list of values).- After the call, assert on usage:
m.assert_called_once_with(...),m.call_count,m.call_args. create_autospec(func)orMock(spec=Cls)restrict the mock to the real signature, so calls with wrong arguments fail instead of silently passing.
To install a mock, either use monkeypatch.setattr(module, "name", fake) or unittest.mock.patch("pkg.module.name") as a context manager. The pytest-mock plugin wraps the latter as a mocker fixture.
The key rule: patch where the name is looked up, not where it's defined. If shop/checkout.py does from shop.payments import charge, patch shop.checkout.charge; patching shop.payments.charge leaves checkout's own reference untouched.
Mock at boundaries (HTTP, clock, filesystem, third-party SDKs), not your own internal functions. Over-mocking produces tests that pass while the real integration is broken, and that break on every refactor. For HTTP specifically, libraries like responses or respx fake the transport more realistically.
Example
# test_checkout.py
from unittest.mock import Mock
class PaymentError(Exception): pass
def checkout(cart_total, gateway):
try:
return gateway.charge(amount=cart_total, currency="EUR")["id"]
except PaymentError:
return None
def test_checkout_charges_total():
gateway = Mock()
gateway.charge.return_value = {"id": "ch_1"}
assert checkout(42, gateway) == "ch_1"
gateway.charge.assert_called_once_with(amount=42, currency="EUR")
def test_checkout_handles_decline():
gateway = Mock()
gateway.charge.side_effect = PaymentError("declined")
assert checkout(42, gateway) is None
def test_patch_module_attr(monkeypatch):
import time
monkeypatch.setattr(time, "time", lambda: 1_700_000_000.0)
assert time.time() == 1_700_000_000.0Remember
- Mock with return_value / side_effect, then assert calls
- Use spec/autospec so mocks enforce real signatures
- Patch the name where it is looked up, not where it's defined
- Mock at system boundaries, not your own internals
Watch out for
- Patching pkg.payments.charge while the code under test imported it with from-import into pkg.checkout.