Mocking #

A good unit test tests one unit of code in isolation — without involving a real database, external API, or file system. The problem: real code almost always depends on something external. Mocking is the technique of replacing those dependencies with stand-in objects whose behavior you fully control: return a specific value, raise an exception, or record whether they were called with the right arguments. Python provides the powerful unittest.mock for this, available directly in the standard library.

Why Mocking Is Necessary #

Without mocking, tests that depend on an external API have many problems:

# ANTI-PATTERN: a test that calls the real API
def test_check_stock():
    result = check_product_stock(product_id=42)  # calls the real API!
    assert result["stock"] > 0

# Problems:
# ✗ Slow test — must wait for the network response
# ✗ Non-deterministic test — results can differ depending on server conditions
# ✗ The test can fail due to network issues, not a code bug
# ✗ Possible side effects: data written to a production database
# ✗ Requires special credentials/environment to run

# CORRECT: mock the external dependency
def test_check_stock():
    with patch("our_module.requests.get") as mock_get:
        mock_get.return_value.json.return_value = {"stock": 10}
        result = check_product_stock(product_id=42)
        assert result["stock"] == 10
# This test is fast, deterministic, and needs no network

To visualize how mocking separates the system under test from side effects and external dependencies, compare the two flows below:

flowchart TD
    subgraph NormalFlow ["Normal Flow (Production)"]
        CodeNormal["Code Under Test"] -->|Call API| RealAPI["Real API / Database (Slow/Changing)"]
        RealAPI -->|Return Response| CodeNormal
    end
    subgraph MockedFlow ["Testing Flow (Mocked)"]
        CodeTest["Code Under Test"] -->|Call API| MockObj["Mock Object (MagicMock)"]
        MockObj -->|Return Predefined Value| CodeTest
    end

Mock and MagicMock #

Mock is a stand-in object that accepts every attribute and call without error — each attribute access automatically produces a new Mock.

from unittest.mock import Mock

m = Mock()

# All attributes automatically exist as new Mocks
print(m.name)           # <Mock name='mock.name' id='...'>
print(m.method())       # <Mock name='mock.method()' id='...'>

# Set a return value
m.return_value = 42
print(m())              # 42

# Set a specific attribute
m.name = "Product A"
print(m.name)           # "Product A"

# Set a nested method's return value
m.fetch_data.return_value = {"id": 1, "name": "Laptop"}
print(m.fetch_data())   # {"id": 1, "name": "Laptop"}

MagicMock is a Mock subclass that already implements Python’s magic methods (__len__, __iter__, __str__, __enter__, __exit__, etc.). Use MagicMock when the code under test interacts with the object using Python protocols.

from unittest.mock import Mock, MagicMock

# Plain Mock — magic methods don't work
m = Mock()
# len(m)  → TypeError: object of type 'Mock' has no len()

# MagicMock — magic methods work
mm = MagicMock()
mm.__len__.return_value = 5
print(len(mm))          # 5

mm.__iter__.return_value = iter([1, 2, 3])
print(list(mm))         # [1, 2, 3]

# MagicMock also supports context managers
mm.__enter__.return_value = mm
mm.__exit__.return_value = False
with mm as obj:
    print(obj)          # <MagicMock ...>

The spec Parameter — Type-Safe Mocks #

Without spec, a mock accepts any attribute — including misspelled names. This can hide bugs:

from unittest.mock import Mock

class EmailService:
    def send(self, to, subject, body):
        pass

# ANTI-PATTERN: without spec — typos go undetected
mock_email = Mock()
mock_email.sned("[email protected]", "Hello", "Message body")  # typo 'sned'!
# The test still passes even though the method name is wrong

# CORRECT: use spec — only attributes on the real class are allowed
mock_email = Mock(spec=EmailService)
mock_email.send("[email protected]", "Hello", "Message body")  # ✓ OK

try:
    mock_email.sned("[email protected]", "Hello", "Body")  # ✗ typo
except AttributeError as e:
    print(e)  # Mock object has no attribute 'sned'
Get in the habit of using spec= or spec_set= when creating mocks. Without it, a mock accepts any attribute name, so typos in method names go undetected and tests can produce false positives — the test passes even though the code is actually wrong.

patch — Temporarily Replacing Dependencies #

patch replaces a real object with a mock for the duration of a test, then restores it to its original state. It can be used as a decorator or a context manager.

The Correct Patching Path Rule #

This is the most common source of confusion: patch where the object is used, not where it’s defined.

# File: product_service.py
import requests  # requests is defined here

def fetch_product(product_id):
    resp = requests.get(f"https://api.example.com/products/{product_id}")
    return resp.json()
# ANTI-PATTERN: patching where requests is defined
@patch("requests.get")                     # ✗ may not work

# CORRECT: patch where requests is used (in the product_service module)
@patch("product_service.requests.get")     # ✓ this is right
A simple rule:
    patch("MODULE_THAT_USES_IT.OBJECT_NAME")
    not
    patch("MODULE_WHERE_OBJECT_ORIGINATES.OBJECT_NAME")

Patch as a Decorator #

import unittest
from unittest.mock import patch, Mock

# product_service.py
import requests

def fetch_product(product_id):
    resp = requests.get(f"https://api.example.com/products/{product_id}")
    data = resp.json()
    if not data:
        raise ValueError("Product not found")
    return data

def create_order(product_id, quantity):
    product = fetch_product(product_id)
    return {"product": product["name"], "quantity": quantity, "total": product["price"] * quantity}


class TestProductService(unittest.TestCase):

    @patch("__main__.requests.get")
    def test_fetch_product_success(self, mock_get):
        # Set the mock response
        mock_get.return_value.json.return_value = {
            "id": 1, "name": "Laptop", "price": 12000000
        }

        result = fetch_product(1)

        self.assertEqual(result["name"], "Laptop")
        # Verify the correct URL was used
        mock_get.assert_called_once_with("https://api.example.com/products/1")

    @patch("__main__.requests.get")
    def test_fetch_product_not_found(self, mock_get):
        mock_get.return_value.json.return_value = {}  # empty response

        with self.assertRaises(ValueError):
            fetch_product(999)

Patch as a Context Manager #

Useful when only a small part of the test needs the mock, or when you want to be more explicit about the mock’s scope.

def test_create_order(self):
    with patch("__main__.requests.get") as mock_get:
        mock_get.return_value.json.return_value = {
            "id": 1, "name": "Mouse", "price": 250000
        }
        order = create_order(product_id=1, quantity=3)

    self.assertEqual(order["total"], 750000)
    self.assertEqual(order["product"], "Mouse")

patch.object — Patching a Method on a Specific Object #

patch.object is more explicit and easier to read because you name the class/module directly, rather than using a string path.

import unittest
from unittest.mock import patch

class EmailService:
    def send(self, to, message):
        # real implementation: send email via SMTP
        pass

class OrderNotification:
    def __init__(self, email_service):
        self.email = email_service

    def send_confirmation(self, order):
        message = f"Order #{order['id']} confirmed. Total: {order['total']}"
        self.email.send(order["buyer_email"], message)
        return True


class TestNotification(unittest.TestCase):

    def test_send_confirmation(self):
        email_service = EmailService()

        with patch.object(EmailService, "send") as mock_send:
            notif = OrderNotification(email_service)
            order = {"id": 42, "total": 150000, "buyer_email": "[email protected]"}
            result = notif.send_confirmation(order)

        self.assertTrue(result)
        mock_send.assert_called_once_with(
            "[email protected]",
            "Order #42 confirmed. Total: 150000"
        )

return_value vs side_effect #

return_value is for returning a fixed value. side_effect is for more dynamic behavior: different values per call, raising exceptions, or running a custom function.

from unittest.mock import Mock

# return_value: always returns the same value
mock = Mock(return_value=100)
print(mock())  # 100
print(mock())  # 100

# side_effect with a list: return a different value each call
mock = Mock(side_effect=[10, 20, 30])
print(mock())  # 10
print(mock())  # 20
print(mock())  # 30
# mock()       # StopIteration — the list is exhausted

# side_effect with an Exception: raise an exception
mock = Mock(side_effect=ConnectionError("Server unreachable"))
try:
    mock()
except ConnectionError as e:
    print(e)  # Server unreachable

# side_effect with a function: custom logic based on arguments
def dynamic_response(url):
    if "products" in url:
        return Mock(json=lambda: {"name": "Laptop"})
    return Mock(json=lambda: {"error": "Not found"})

mock_get = Mock(side_effect=dynamic_response)
print(mock_get("https://api.example.com/products/1").json())  # {"name": "Laptop"}
print(mock_get("https://api.example.com/other").json())       # {"error": "Not found"}

Mock Assertions — Verifying Calls #

After the code runs, you can verify whether the mock was called correctly.

from unittest.mock import Mock, call

mock = Mock()

# Call the mock several times
mock("first", key="value")
mock("second")

# Verify the last call
mock.assert_called_with("second")

# Verify the first call (using call_args_list)
self.assertEqual(mock.call_args_list[0], call("first", key="value"))

# Verify the call count
self.assertEqual(mock.call_count, 2)

# Verify it was called exactly once with specific arguments
mock2 = Mock()
mock2("the-only-one")
mock2.assert_called_once_with("the-only-one")

# Verify it was NEVER called
mock3 = Mock()
mock3.assert_not_called()

Real Case: Mocking datetime.now() #

Testing code that depends on the current time has its own gotcha — you can’t patch datetime.datetime directly because it’s a C built-in type.

# report_module.py
from datetime import datetime

def create_report(data):
    created_at = datetime.now().strftime("%Y-%m-%d %H:%M")
    return {"time": created_at, "data": data}
import unittest
from unittest.mock import patch
from datetime import datetime

class TestReport(unittest.TestCase):

    @patch("report_module.datetime")
    def test_create_report(self, mock_datetime):
        # patch datetime in the module that uses it
        mock_datetime.now.return_value = datetime(2025, 6, 15, 10, 30)

        result = create_report({"item": "Laptop"})

        self.assertEqual(result["time"], "2025-06-15 10:30")
        self.assertEqual(result["data"], {"item": "Laptop"})

Mocking Anti-Patterns to Avoid #

# ✗ Anti-pattern 1: mocking too much — the test doesn't test anything real
def test_create_order():
    with patch("module.fetch_product") as mp, \
         patch("module.compute_total") as mh, \
         patch("module.save_to_db") as ms, \
         patch("module.send_notification") as mn:
        mp.return_value = {"name": "Laptop"}
        mh.return_value = 12000000
        ms.return_value = True
        mn.return_value = True
        result = create_order(1, 1)
        # This test only proves the function calls the mocks — not the business logic

# ✓ Solution: mock only external dependencies (I/O, APIs), test the real business logic

# ✗ Anti-pattern 2: patching without spec — hides typos
mock_db = Mock()
mock_db.savee(data)  # typo 'savee' undetected, the test still passes

# ✓ Solution: always use spec=
mock_db = Mock(spec=DatabaseService)

# ✗ Anti-pattern 3: assert_called_once_with after many calls
mock.method("a")
mock.method("b")
mock.method.assert_called_once_with("b")  # FAILS — called twice, not once

# ✓ Solution: use assert_called_with (last call) or inspect call_args_list
mock.method.assert_called_with("b")       # verifies the last call

Summary #

  • Mocks replace external dependencies so tests are fast, deterministic, and isolated from the network, database, or external systems.
  • Mock for regular objects; MagicMock for objects using magic methods (len(), iter(), context managers).
  • Always use spec= — without it, attribute/method name typos go undetected and tests can produce false positives.
  • The patching path rule: patch where the object is used, not where it’s definedpatch("our_module.requests.get"), not patch("requests.get").
  • patch.object is more explicit and readable for mocking a method on a specific class or instance.
  • return_value for fixed values; side_effect for different values per call, raising exceptions, or custom logic.
  • Verify mock calls with assert_called_once_with, assert_called_with, assert_not_called, and call_count.
  • Don’t over-mock — if almost every dependency is mocked, the test no longer checks integration between components and can give false confidence.

← Previous: Unit Testing   Next: JSON →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact