Python: Mock an inner import

That seals the deal.

Sometimes, you want to test a function that uses an inner import and mock that imported object. For example, say you wanted to mock dig() in this example:

def delve():
    from digger import dig

    dig(greedily=True, depth=3000)

The typical mocking pattern would be to use mock.patch("example.dig"), but that does not work here. That mock will only replace the dig name in the module namespace. When the function runs, it reimports dig into its local namespace, unaffected by the module-level dig name.

Here are two patterns that do work.

Mocking on the imported module

Use mock.patch.object() to replace the imported name from the target module:

from unittest import mock

import digger
from example import delve


def test_delve():
    with mock.patch.object(digger, "dig", autospec=True) as mock_dig:
        delve()

    assert mock_dig.mock_calls == [
        mock.call(greedily=True, depth=3000),
    ]

The inner import pulls the mock object from the real digger module, for which we replaced dig with a mock. Using autospec ensures the mock object works like the target function—see “Autospeccing” in the docs.

Mocking the whole imported module

You may wish to avoid the inner import loading its target module altogether. Perhaps it is expensive to import or not always installed in the test environment. In this case, create a mock module object and place it in sys.modules.

sys.modules is Python’s dictionary of all imported modules. You can manipulate it to affect later import statements:

In [1]: import sys

In [2]: sys.modules["digger"] = "whatever"

In [3]: import digger

In [4]: digger
Out[4]: 'whatever'

Use mock.patch.dict() on sys.modules to temporarily install a mock module object:

import sys
from unittest import mock

from example import delve


def test_delve():
    mock_digger = mock.Mock(dig=mock.MagicMock())

    with mock.patch.dict(sys.modules, {"digger": mock_digger}):
        delve()

    assert mock_digger.dig.mock_calls == [
        mock.call(greedily=True, depth=3000),
    ]

MagicMock() is required here because dig() is a function and only MagicMock is callable.

Use this method with caution. The mock module is entirely separate from the actual module, so tests won’t fail for any relevant changes to it.

Fin

Don’t mock the frock,

—Adam


😸😸😸 Check out my new book on using GitHub effectively, Boost Your GitHub DX! 😸😸😸


Subscribe via RSS, Twitter, Mastodon, or email:

One summary email a week, no spam, I pinky promise.

Related posts:

Tags: