Speed up your test suite

Expensive mock setup (unittest.mock, autospec)

mock.patch with autospec=True, create_autospec and deep MagicMock chains cost real time when repeated in every test. Patch once per class or module and reuse spec'd mocks.

Mocks feel free, but they aren't. Every Mock is a Python object with its own class, every attribute you touch on it creates another mock, and autospec=True inspects the signature of every method on the object it copies. One patch in one test costs microseconds to milliseconds. The same patch in the setUp of a large class, inside a loop, or on a big class with autospec, repeated across thousands of tests, adds up to seconds.

Why mocks get expensive

  • Autospec. mock.patch(..., autospec=True) and create_autospec(SomeClass) go through dir() of the spec, fetch every attribute (properties run), and for every method create a child MagicMock with its own return-value mock and a copy of the method's signature. A class with hundreds of methods, like a Django model, an ORM session or an SDK client, makes that hundreds of mocks, built again every time the patch starts.
  • A new class per mock. unittest.mock gives every mock instance its own subclass, so it can attach magic methods without affecting other mocks. Creating thousands of mocks creates thousands of classes.
  • Deep attribute chains. client.orders.list.return_value.data.items.return_value = [...] creates a mock for every step.
  • Patching in loops and per test. A patch inside a for loop or a parametrized test starts and stops every iteration; a dozen stacked @mock.patch decorators do all of that for every test.

Patch once per class or module

When every test in a class or module needs the same patch, start it once.

unittest / Django TestCase: setUpClass

A class-level @mock.patch(...) decorator doesn't help here: it wraps each test method separately, so the patch still starts and stops once per test. Start the patcher in setUpClass instead:

from unittest import mock

from django.test import TestCase
from model_bakery import baker

from shop.checkout import checkout
from shop.models import Order


class CheckoutTests(TestCase):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        patcher = mock.patch("shop.checkout.PaymentGateway", autospec=True)
        cls.gateway_class = patcher.start()
        cls.addClassCleanup(patcher.stop)

    @classmethod
    def setUpTestData(cls):
        cls.order = baker.make(Order, total=100)

    def setUp(self):
        self.gateway_class.reset_mock()

    def test_charges_the_order_total(self):
        checkout(self.order)
        self.gateway_class.return_value.charge.assert_called_once_with(amount=100)

The autospec is built once for the class. reset_mock() clears the recorded calls between tests (on the child mocks too), but keeps configured return values and side effects. If a test sets side_effect, undo it in that test or in setUp. Avoid reset_mock(return_value=True) on an autospecced class: it throws away the spec'd instance mock along with the return value.

pytest: a module-scoped fixture

The monkeypatch and mocker fixtures are function-scoped. For a wider scope use pytest.MonkeyPatch.context(), or pytest-mock's module_mocker / class_mocker / session_mocker:

import pytest


@pytest.fixture(scope="module")
def gateway_class(module_mocker):
    return module_mocker.patch("shop.checkout.PaymentGateway", autospec=True)


@pytest.fixture
def gateway(gateway_class):
    gateway_class.reset_mock()
    gateway = gateway_class.return_value
    yield gateway
    gateway.charge.side_effect = None

Tests take gateway and get a mock with no recorded calls, while the expensive patch runs once per module. Reset whatever your tests configure (here, a side_effect on charge) at the end of the fixture.

Reuse spec'd mocks

When code receives a dependency as an argument, you don't need to patch at all, but create_autospec per test still costs the same. Build the spec'd mock once and reset it, as above. For a spec that's only used to catch typos in attribute names, mock.Mock(spec=SomeClass) is cheaper than create_autospec: it restricts attributes to the names in dir(SomeClass) without building a child mock and a signature check for every method up front. Use autospec where signatures matter (catching a call with the wrong arguments), not by default everywhere.

Simplify what you mock

  • Configure chains once: orders = client.orders.list.return_value then set attributes on orders, instead of repeating the full chain.
  • Use a real object or a small fake class instead of a deeply configured MagicMock. A dataclass with three fields is faster and easier to read than a mock with three configured attributes.
  • Move patches out of loops: patch once around the loop, and change return_value or side_effect inside it.
  • Patch at the transport, not every function: for HTTP, one responses or respx mock replaces a stack of patched client functions. See mock and block real network calls.

Keep the tests correct

  • A mock shared across a class or module records calls from every test. Reset it before each test (reset_mock()), and undo any return values or side effects a test configures, or assertions see other tests' calls and setups.
  • Never let a wider-scoped patch leak: stop it with addClassCleanup, or use a fixture that undoes it at the end of its scope. Run the suite in random order (pytest-randomly) to catch leaks.
  • Replacing autospec with a plain Mock loses signature checking: a call with wrong arguments passes. Keep autospec on the boundaries where that matters.

How flakytest finds it

flakytest puts time spent in unittest.mock, pytest-mock, freezegun, time-machine, responses and respx in the mocking category. When mocking (not counting freezegun, which has its own suggestion) takes more than a second and 3% of test time, it suggests Mock setup is expensive. If more than 0.2 seconds of it is in autospec (create_autospec and signature checks), the summary says how much. The suggestion lists the tests and the lines of your code that create the most expensive mocks, so you can see which patch to move up a level.

Further reading

Related guides

  • Expensive function-scoped pytest fixtures: A pytest fixture without a scope runs again for every test that uses it. Give expensive fixtures module or session scope, or split them into a shared part and a cheap reset.
  • Replace freezegun with time-machine: freezegun scans every loaded module each time it freezes time, which gets slow in big projects. time-machine mocks time at the C level. How to migrate, including tick=False.
  • Mock and block real network calls in tests: Real HTTP calls make pytest suites slow and flaky. Mock at the transport with responses, respx, pytest-httpx, aioresponses, moto or vcrpy, then block the network with pytest-socket.
  • Create shared test data in setUpTestData instead of setUp: Django runs setUp before every test, so its rows are inserted again each time. setUpTestData creates them once per TestCase class, and every test still starts clean.

Updated October 18, 2026 · Suggest a change