Patching the Right Thing with mocker

Published: November 30, 2025


Short answer: mocker is a pytest fixture provided by the pytest-mock plugin.

It provides a small helper object (usually called mocker) that wraps Python’s standard library unittest.mock so you can:

  • Replace (“patch”) real objects (functions, methods, attributes) with controllable MagicMock fakes.
  • Track calls: check how many times a fake ran, which arguments it saw, and related details.
  • Automatically undo all patches at the end of each test so your code returns to normal.

1. Code Example

email_gateway.py

import smtplib
from email.message import EmailMessage


def send_email(recipient: str, subject: str, body: str) -> str:
    """Deliver transactional email through our SMTP relay."""
    # deliver a message via SMTP
    message = EmailMessage()
    message["From"] = "noreply@example.com"
    message["To"] = recipient
    message["Subject"] = subject
    message.set_content(body)

    try:
        with smtplib.SMTP("mail.example.com", 587, timeout=3) as client:
            client.starttls()
            client.login("noreply@example.com", "super-secret")
            client.send_message(message)
    except OSError:
        # Swallow transport errors for this illustrative example.
        pass

    return f"sent:{recipient}:{subject}"

email_service.py

from email_gateway import send_email


def notify_user(recipient: str, message: str) -> str:
    """Send the standard account-update email copy to a user."""
    subject = "Account update"
    body = f"Hello!\n\n{message}"
    return send_email(recipient, subject, body)

Because the high-level notify_user function imports send_email and closes over that name, tests patch email_service.send_email, instead of email_gateway.send_email.

2. What should be the target?

Rule of thumb:

Patch the name visible inside the module under test, not the original place where the object was first defined. In other words, patch “where it is used” instead of “where it is defined”.


3. A minimal custom PocketMocker

Now let’s build a mini version of mocker to show all the moving parts.

We won’t touch unittest.mock.patch; instead we’ll do the work manually with:

  • importlib.import_module
  • getattr / setattr
  • unittest.mock.MagicMock

pocket_mocker.py

import importlib
from unittest.mock import MagicMock


class PocketMocker:
    """Tiny helper to mimic the patching API readers see from pytest-mock."""

    def __init__(self):
        self._stack = []

    def patch(self, target: str, replacement=None, **kwargs):
        module_path, attribute = target.rsplit(".", 1)
        module = importlib.import_module(module_path)
        original = getattr(module, attribute)
        if replacement is None:
            replacement = MagicMock(**kwargs)
        setattr(module, attribute, replacement)
        self._stack.append((module, attribute, original))
        return replacement

    def stopall(self):
        while self._stack:
            module, attribute, original = self._stack.pop()
            setattr(module, attribute, original)

The low-level helpers explained

  • importlib.import_module(module_path)

    • Takes a string such as "email_service.send_email" and returns the actual module object.
    • Relies on Python’s import machinery and sys.modules.
  • getattr(module, attr_name)

    • Reads an attribute on an object.
    • Equivalent to module.attr_name.
  • setattr(module, attr_name, replacement)

    • Sets an attribute on an object.
    • Equivalent to module.attr_name = replacement.
    • This is the literal “patch” step: we overwrite the original function with our fake.
  • MagicMock(**kwargs)

    • Creates a flexible fake object that:

      • Records calls (so you can assert later).
      • Can be configured with return_value or side_effect.
      • Supports attributes and methods dynamically.

Building PocketMocker shows that pytest-mock plus unittest.mock are simply a nicer layer over this same core behavior.

Tests

test_email_service.py

import email_service
from pocket_mocker import PocketMocker


def test_notify_user_with_pytest_mocker(mocker):
    mocker.patch(
        "email_service.send_email", return_value="sent:me@example.com:Account update"
    )
    result = email_service.notify_user("me@example.com", "You have a new badge!")
    assert result == "sent:me@example.com:Account update"


def test_notify_user_with_pocket_mocker():
    pocket = PocketMocker()
    pocket.patch(
        "email_service.send_email", return_value="sent:you@example.com:Account update"
    )

    result = email_service.notify_user("you@example.com", "Password changed")
    assert result == "sent:you@example.com:Account update"

    pocket.stopall()

Test output

test_email_service.py ..
============================= 2 passed in 0.05s =============================

The first test exercises the real pytest-mock fixture, while the second walks through the DIY implementation so you can trace every step.

Research Notes

Expand to read the full research notes, references, and comparisons gathered for this topic.