Skillversitas

Mocking with unittest.mock

The mock is the dependency stub: the unittest.mock.patch replaces the target (the function, the method, the module) with the MagicMock (the auto-attribute, the return_value the configured), and the test asserts the call (the assert_called_once_with, the call_count). The patch is the context manager or the decorator (the scope the test, the restore on the exit), the patch.object the instance, the patch.dict the os.environ, and the pytest-mock plugin adds the mocker fixture (the mocker.patch the no-decorator form). This chapter covers the patch (the context, the decorator, the as), the return_value/side_effect, the call assertion (assert_called, the call_args), and the pytest-mock the mocker fixture.

The patch: The Context and the Decorator

# the patch: the the REPLACE (the target
#   the the MagicMock, the
#   restore on the exit)
from unittest.mock import patch, MagicMock
from texttools import export_words

def test_export_calls_writer():
    # the CONTEXT (the scope, the
    #   the restore on the with-exit):
    with patch("texttools.io.write_file") as w:
        w.return_value = None
        export_words("a b", "out.txt")
        w.assert_called_once_with("out.txt", "a b")
    # the w: the the MagicMock (the
    #   the call RECORD, the)

# the DECORATOR (the test, the
#   the):
@patch("texttools.io.write_file")
def test_export_decorator(w):
    export_words("a b", "out.txt")
    w.assert_called_once()
# the the w: the the MagicMock the
#   the

The patch is the context manager or the decorator: the patch("texttools.io.write_file") replaces the target (the write_file the texttools.io module) with the MagicMock, and the as w (the context) or the test argument (the decorator) hands the mock - the w.return_value = None configures, the export_words calls the mock (the no real I/O), and the w.assert_called_once_with("out.txt", "a b") asserts the call (the target, the args, the once). The patch.object patches the instance attribute (the patch.object(instance, "method")), the patch.dict(os.environ, {...}) the environment, and the restore is automatic (the with-exit, the decorator's after) - the mock's scope is the test, the no global leak. The rule is the one: the patch the target in the module under test (the texttools.io.write_file, not the texttools.io the import source - the where it is looked up), and the next section's side_effect is the mock's behavior beyond the return_value.

The return_value and the side_effect

# the return_value: the the FIXED (the
#   the)
from unittest.mock import patch, MagicMock

def test_fetch_value():
    with patch("texttools.net.fetch") as f:
        f.return_value = "the body"
        assert fetch_text("url") == "the body"

# the side_effect: the the CALLABLE / the the ITERABLE
# (the per-call, the
#   the)
def test_fetch_side():
    with patch("texttools.net.fetch") as f:
        f.side_effect = ["first", "second"]
        assert fetch_text("url") == "first"
        assert fetch_text("url") == "second"
    # the RAISE: the side_effect the
    #   the EXCEPtion (the)
    with patch("texttools.net.fetch") as f:
        f.side_effect = ConnectionError("the down")
        with pytest.raises(ConnectionError):
            fetch_text("url")
# the call RECORD: the the mock's the
#   the call_args (the last),
#   the mock_calls (the all),
#   the call_count (the int)

The return_value is the fixed (the every call returns the same, the f.return_value = "the body"), and the side_effect is the per-call: the callable (the lambda url: f"body-{url}" the computed), the iterable (the ["first", "second"] the per-call pop, the StopIteration the exhausted), or the exception (the side_effect = ConnectionError the raise). The call record is the mock's memory: the call_args (the last call's args/kwargs, the call_args[0] the tuple, the call_args[1] the dict), the mock_calls (the all, the order), and the call_count (the int) - the assertion on the behavior (the assert_called_once_with, the assert_any_call). The autospec=True is the signature-check (the patch(..., autospec=True) the wrong-arg the TypeError), and the pytest-mock (the next section) is the fixture form - the two are the mock's behavior and the mock's interface, the next section's mocker the fixture the no-decorator.

The pytest-mock: The mocker Fixture

# the pytest-mock: the the mocker FIXTURE (the
#   the no-decorator, the
#   the)
import pytest

def test_export_mocker(mocker):
    w = mocker.patch("texttools.io.write_file")
    w.return_value = None
    export_words("a b", "out.txt")
    w.assert_called_once_with("out.txt", "a b")
    # the mocker: the MagicMock the
    #   the
    #   the

def test_env_mocker(mocker):
    mocker.patch.dict("os.environ", {"CI": "1"})
    assert os.environ.get("CI") == "1"
    # the restore: the
    #   the

The pytest-mock plugin adds the mocker fixture: the mocker.patch is the no-decorator form (the w = mocker.patch("texttools.io.write_file") the mock, the no @patch, the no with), the mocker.patch.object the instance, the mocker.patch.dict the os.environ (the {"CI": "1" the set, the restore on the teardown), and the mocker.MagicMock the bare mock the test builds. The fixture scope is the function (the fresh per test, the auto-restore), and the rule is the same as the patch: the target in the module under test (the texttools.io.write_file the lookup, not the texttools.io the source). The mocker is the fixture-first (the no decorator clutter, the) and the patch the context/decorator (the explicit) - the two are the same unittest.mock, and the next chapter's monkeypatch is the lighter stub the attribute and the env the no mock object.

The Working Rules

  • The patch is the context or the decorator, the restore automatic. The patch("texttools.io.write_file") the MagicMock, the as w the mock (the context) or the test argument (the decorator), the with-exit/after the restore - the scope the test, the no global leak.
  • The patch the target in the module under test. The texttools.io.write_file (the lookup), not the texttools.io (the source) - the where it is looked up, the patch.object the instance, the patch.dict the os.environ.
  • The return_value the fixed, the side_effect the per-call. The callable the computed, the iterable the per-call pop, the exception the raise - the call_args the last, the mock_calls the all, the call_count the int, the autospec the signature-check.
  • The pytest-mock the mocker fixture, the no-decorator. The mocker.patch the mock, the mocker.patch.dict the os.environ (the restore), the function scope the fresh - the fixture-first the mocker, the explicit the patch.

Summary

The mock is the dependency stub: the unittest.mock.patch replaces the target (the texttools.io.write_file the lookup, not the source) with the MagicMock, and the return_value the fixed, the side_effect the per-call (the callable, the iterable, the exception). The patch is the context manager or the decorator (the as w the mock, the test argument the decorator's, the restore automatic), the patch.object the instance, the patch.dict the os.environ, and the call record is the call_args (the last), the mock_calls (the all), the call_count (the int) - the assert_called_once_with the behavior assertion, the autospec the signature-check. The pytest-mock plugin adds the mocker fixture (the mocker.patch the no-decorator, the mocker.patch.dict the os.environ the restore, the function scope) - the fixture-first the mocker, the explicit the patch. The next chapter's monkeypatch is the lighter stub: the attribute and the env the no mock object, the setattr/setenv the direct replace.