mocking imports
unit testing
software testing
Python programming
test-driven development

How to mock an import

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Mocking an import in Python tests is really about replacing the dependency at the place where the code under test looks it up. If you patch the wrong path, the real dependency still runs, and the test becomes flaky, slow, or unexpectedly stateful.

Patch Where the Symbol Is Used

This is the rule that matters most. If service.py imports fetch_data from client.py, then tests should patch service.fetch_data, not client.fetch_data.

python
1# service.py
2from client import fetch_data
3
4def load_user(user_id: str):
5    return fetch_data(user_id)
python
1# test_service.py
2from unittest.mock import patch
3import service
4
5def test_load_user_uses_mock():
6    with patch("service.fetch_data", return_value={"id": "u1"}) as mock_fetch:
7        result = service.load_user("u1")
8        assert result["id"] == "u1"
9        mock_fetch.assert_called_once_with("u1")

Patching client.fetch_data would not affect service.load_user after the import binding has already happened.

Patch Module Attributes With patch.object

If the module imports another module rather than a symbol, patch the attribute on that imported module object.

python
1# calc.py
2import random
3
4def roll():
5    return random.randint(1, 6)
python
1from unittest.mock import patch
2import calc
3
4def test_roll():
5    with patch.object(calc.random, "randint", return_value=4):
6        assert calc.roll() == 4

This is a clean pattern when the dependency is used as a module attribute rather than bound directly into the local namespace.

Replace Entire Modules With sys.modules

Sometimes you need to fake an optional dependency or inject a synthetic module before import time. In that case, sys.modules can be useful.

python
1import sys
2import types
3
4fake_module = types.SimpleNamespace(run=lambda: "ok")
5sys.modules["external_plugin"] = fake_module
6
7import external_plugin
8print(external_plugin.run())

In real tests, use fixtures or context-managed setup so the fake module does not leak into unrelated tests.

Use autospec When You Want Stricter Mocks

Loose mocks are convenient, but they can silently accept wrong arguments. autospec=True makes the patched object respect the original signature more closely.

python
1from unittest.mock import patch
2
3with patch("service.fetch_data", autospec=True) as mock_fetch:
4    mock_fetch.return_value = {"id": "u1"}

This helps catch call-signature drift when real functions change over time but tests still pass because the mock accepts anything.

Sometimes the Best Fix Is Better Design

Heavy import mocking often signals tight coupling. If a class or function can accept a dependency explicitly, tests become simpler and less fragile.

python
1class UserService:
2    def __init__(self, client):
3        self.client = client
4
5    def load(self, user_id):
6        return self.client.fetch(user_id)

Now the test can provide a fake client directly instead of patching imports deep in the module graph. That usually leads to clearer tests and cleaner production code.

Pytest Fixtures Make Repeated Mocking Cleaner

In pytest projects, fixtures are a good way to centralize common patches.

python
1import pytest
2from unittest.mock import patch
3
4@pytest.fixture
5def mocked_fetch():
6    with patch("service.fetch_data", return_value={"id": "fixture-user"}) as mock_fetch:
7        yield mock_fetch
8
9def test_load_user_with_fixture(mocked_fetch):
10    import service
11    result = service.load_user("abc")
12    assert result["id"] == "fixture-user"

This avoids duplicated setup code and makes teardown automatic.

Validate More Than Call Counts

A mock assertion such as assert_called_once_with is useful, but it should not be the only assertion. Good tests usually check both how the dependency was called and what the business logic returned.

That matters because a test can pass on interaction alone while still hiding a broken return value, incorrect transformation, or swallowed exception.

Common Pitfalls

The biggest mistake is patching the definition site instead of the usage site. In Python, import binding means those are often different paths.

Another issue is leaving patched objects active beyond the intended scope. Leaky mocks contaminate other tests and are hard to diagnose in larger suites.

Developers also overuse mocks for simple collaborators that could just be lightweight fake objects. Excessive patching can make tests obscure and brittle.

Finally, if an import is hard to mock cleanly, that is often a design hint. The code may need a clearer dependency boundary rather than a more clever patch.

Summary

  • Patch the dependency where the code under test looks it up, not where it was originally defined.
  • Use patch.object when a module attribute is the thing being called.
  • Use sys.modules injection only when you need to fake an entire module at import time.
  • Add autospec when you want mocks to respect real call signatures.
  • Prefer explicit dependency injection when mocking imports starts to dominate the test design.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.