45

I am writing a pytest plugin that should test software that's designed to work inside a set of specific environments.

The software I'm writing is run inside a bigger framework, which makes certain Python modules available only when running my Python software inside the framework.

In order to test my software, I'm required to "mock" or fake an entire module (actually, quite a few). I'll need to implement its functionality in some kind of similar-looking way, but my question is how should I make this fake Python module available to my software's code, using a py.test plugin?

For example, let's assume I have the following code in one of my source files:

import fwlib

def fw_sum(a, b):
    return fwlib.sum(a, b)

However, the fwlib module is only made available by the framework I run my software from, and I cannot test inside it.

How would I make sure, from within a pytest plugin, that a module named fwlib is already defined in sys.modules? Granted, I'll need to implement fwlib.sum myself. I'm looking for recommendations on how to do just that.

3 Answers 3

56

pytest provides a fixture for this use-case: monkeypatch.syspath_prepend.

You may prepend a path to sys.path list of import locations. Write a fake fwlib.py and include it in your tests, appending the directory as necessary. Like the other test modules, it needn't be included with the distribution.

After playing with this myself, I couldn't actually figure out how to get the fixture to mock module level imports correctly from the library code. By the time the tests run, the library code was already imported and then it is too late to patch.

However, I can offer a different solution that works: you may inject the name from within conftest.py, which gets imported first. The subsequent import statement within the code under test will just re-use the object already present in sys.modules.

Package structure:

$ tree .
.
├── conftest.py
├── lib
│   └── my_lib.py
└── tests
    └── test_my_lib.py

2 directories, 3 files

Contents of files:

# conftest.py
import sys

def fwlib_sum(a, b):
    return a + b

module = type(sys)('fwlib')
module.sum = fwlib_sum
sys.modules['fwlib'] = module

library file:

# lib/my_lib.py
import fwlib

def fw_sum(a, b):
    return fwlib.sum(a, b)

test file:

# lib/test_my_lib.py
import my_lib

def test_sum():
    assert my_lib.fw_sum(1, 2) == 3
Sign up to request clarification or add additional context in comments.

10 Comments

This is excellent, thank you! Could you please clarify the last statement, about needn't it be included with the distribution?
Presumably you have a ./tests/ subdirectory, that is not packaged up by your setup.py file when creating a distribution. You may create for example ./tests/mocks/fwlib.py that can be imported during test runs, but it's not distributed with your library.
Ah, I see. I'll wait a bit for other answers to come (however unlikely) and accept your soon. Thanks again!
Does anyone know how to do this when the import is like "from <module> import <function>"?
@wim You may have many other tests which conflict with the action of changing module globally.
|
10

Just to provide a little more details to @wim's good answer, you can use it with submodules too, like so:

import sys
module = type(sys)("my_module_name")
module.submodule = type(sys)("my_submodule_name")
module.submodule.something = sommething
sys.modules["my_module_name"] = module
sys.modules["my_module_name.my_submodule_name"] = module.submodule

Comments

5

Also, based on @Itération's answer, I found you can also use fixtures like so to avoid polluting all tests:

# conftest.py
@pytest.fixture()
def mock_lib_installed():
    def lib_func():
        print("lib_func called")

    module = type(sys)("my_module_name")
    module.submodule = type(sys)("submodule")
    module.submodule.lib_func = lib_func
    sys.modules["my_module_name"] = module
    sys.modules["my_module_name.submodule"] = module.submodule

    yield

    del sys.modules["my_module_name"]
    del sys.modules["my_module_name.submodule"]

1 Comment

Note that this will only work if the module is imported while running the test. If it is imported at the top of the file like normal, then the fixture won't be in place when the import happens, and if the real module is not available (as is the case in this question), then it will fail

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.