Backend / Testing / pytest / 05_mock_vs_magicmock_patch.md

Mock, MagicMock, patch

Updated 4 min read source
On this page12
  1. Mock vs MagicMock
  2. return_value vs side_effect
  3. patch — replace at a location
  4. patch where it’s used, not where it’s defined
  5. patch.object
  6. patch.dict
  7. autospec — type-safety mocks
  8. spec vs autospec
  9. AsyncMock
  10. Asserting calls
  11. Common patterns
  12. Interview angle

Mock, MagicMock, patch

unittest.mock is the standard mocking library. Three things to keep straight: what to instantiate, how to install it, and where to install it.

Mock vs MagicMock

python
from unittest.mock import Mock, MagicMock

m = Mock()
m.foo()         # OK — auto-creates `.foo`, returns another Mock
m()             # OK — m is callable
len(m)          # TypeError: object of type 'Mock' has no len()
m["key"]        # TypeError

mm = MagicMock()
len(mm)         # 0 — supports __len__
mm["key"]       # MagicMock — supports __getitem__
mm + 1          # MagicMock — supports __add__

MagicMock pre-configures magic methods (dunders): __len__, __iter__, __getitem__, __contains__, __enter__, __exit__, etc. Use it for objects that need to behave like containers, context managers, or numerics.

Mock is leaner — use when you don’t need dunders.

return_value vs side_effect

python
m = Mock()
m.return_value = 42
m()  # 42
m()  # 42

return_value is what the mock returns when called.

side_effect is more flexible:

python
# Iterable: each call pops the next value
m.side_effect = [1, 2, 3]
m(); m(); m()        # 1, 2, 3
m()                   # raises StopIteration

# Exception class or instance: raised
m.side_effect = ValueError("boom")
m()                   # raises ValueError

# Callable: invoked with the call args
m.side_effect = lambda x: x * 2
m(5)                  # 10

When both are set, side_effect wins, unless side_effect returns DEFAULT, in which case return_value is used.

patch — replace at a location

python
from unittest.mock import patch

@patch("myapp.api.requests.get")
def test_call(mock_get):
    mock_get.return_value.json.return_value = {"ok": True}
    result = myapp.api.fetch()
    assert result == {"ok": True}

patch is a context manager / decorator that swaps an attribute on a module for the duration. Decorator argument order is reverse of decorator stacking:

python
# innermost decorator → first arg
@patch("myapp.b")
@patch("myapp.a")
def test(mock_a, mock_b):
    ...

patch where it’s used, not where it’s defined

This is the #1 mock gotcha.

python
# myapp/api.py
import requests
def fetch():
    return requests.get("https://...").json()
python
# Wrong: patches the requests module, but myapp.api already imported it
@patch("requests.get")
def test_fetch(mock_get): ...

# Right: patches the name as it lives in myapp.api
@patch("myapp.api.requests.get")
def test_fetch(mock_get): ...

from x import y binds y in the importing module’s namespace. Patching x.y changes the original; the importing module still references the old object.

patch.object

When you have the object in a variable, not a string path:

python
import myapp.api as api
with patch.object(api, "requests") as mock_requests:
    mock_requests.get.return_value.json.return_value = {"ok": True}
    api.fetch()

Equivalent to patch("myapp.api.requests") but the path is a Python expression rather than a string. Type checkers like it better.

patch.dict

For mutable mappings (notably os.environ):

python
@patch.dict("os.environ", {"API_KEY": "test-key"})
def test_api():
    ...

# Or as context manager:
with patch.dict(os.environ, {"API_KEY": "test"}, clear=True):
    ...

clear=True empties the dict before applying the patch. Restored on exit.

autospec — type-safety mocks

python
@patch("myapp.api.requests.get", autospec=True)
def test(mock_get):
    # TypeError: requests.get got unexpected kw 'badarg'
    mock_get(123, badarg="x")

autospec=True inspects the real function’s signature. Calls with wrong arguments fail at test time, just like in production. Highly recommended.

For classes, autospec also restricts which attributes/methods exist:

python
@patch("myapp.client.HTTPClient", autospec=True)
def test(MockClient):
    instance = MockClient.return_value
    instance.connect()        # OK
    instance.nonexistent()    # AttributeError

spec vs autospec

spec=SomeClass constrains the mock’s attributes to match the class. autospec=True also auto-spec’s signatures. spec_set= is spec with attribute setting also blocked.

In practice: prefer autospec=True. It catches more bugs.

AsyncMock

For coroutines, use AsyncMock. Pre-configured to return coroutines.

python
from unittest.mock import AsyncMock

@patch("myapp.api.fetch_data", new_callable=AsyncMock)
async def test_call(mock_fetch):
    mock_fetch.return_value = {"ok": True}
    result = await myapp.api.fetch_data()
    assert result == {"ok": True}

In Python 3.8+ MagicMock auto-detects async methods on a spec and uses AsyncMock for them.

See pytest-asyncio.

Asserting calls

python
mock.assert_called()                             # called at least once
mock.assert_called_once()                        # exactly once
mock.assert_called_with(1, 2, key="x")            # last call args
mock.assert_called_once_with(1, 2)               # exactly once + args
mock.assert_any_call(...)                        # any historical call

mock.call_count                                   # int
mock.call_args                                    # last call's call(args, kwargs)
mock.call_args_list                              # all calls

Common typo bug: assert_called_with requires the underscore — assert mock.called_with(...) silently auto-creates a sub-mock and always passes. Use autospec=True (or from unittest.mock import call) to catch this.

Common patterns

Mock returning a chained call:

python
mock_get.return_value.json.return_value = {"ok": True}
# now: mock_get(...).json() == {"ok": True}

Recording call sequences:

python
mock.call_args_list == [
    call(1, 2),
    call(3, 4),
]

Reset between assertions:

python
# clears call history; keeps return_value/side_effect
mock.reset_mock()

Interview angle 4

  • Q: “Difference between Mock and MagicMock?” — MagicMock pre-configures dunders.
  • Q: “What’s return_value vs side_effect?” — fixed value vs callable/exception/iterable.
  • Follow-up: “Where do you patch — at definition or use site?” — use site. patch("myapp.api.requests.get") not patch("requests.get").
  • Follow-up: “What does autospec=True give you?” — signature checking, attribute restriction, catches “called with wrong args” bugs.

See Mocking external APIs for end-to-end mock examples, Test doubles taxonomy for the broader taxonomy.