Injecting modules into tests with Pytest

George Shuklin
OpsOps
Published in
2 min readJun 23, 2024

There is a known slightly boring problem with tests. You have conftest.py, which is magical and is always available in tests (but only via hooks and fixtures, and double-magical ‘request’ metafunc). If you want to have it outside fixtures, things become complicated.

You can have an additional library, but if your tests are in complicated structure (like this):

tests/
|- infra/
|- monitoring/
|- logging/
|- security/
|- applications/
|- foo/
|- smoke
|- long

then you have a problem with import. You can get away with it (relative pathes, plus __init__.py everywhere), but it’s not elegant, and it slows down restructuring tests. You no longer can just ‘mv’ them, you need to update imports.

The thing I wanted, in any test file, at any nested directory, ability to write:

from mytestlib import singleton

And I want this singleton been created by conftest (because everything interesting is happening there).

I had had few tricks to do it before, but they all was… meh.

Now a cool one, credited to… OpenAI. (And my persistence on correcting hallucinations; turned out, one of hallucinations was real).

In the conftest.py

class MyTestLib:
@property
def singleton(self):
call_to_conftest_internals()

sys.modules["mytestlib"] = MyTestLib()

That’s all. Now, every test module (a file *test*.py discovered by pytest) can do this:

from mytestlib import singleton

This magic with class pretending to be a module… There is beauty in Python.

Why? Why do you need it when you have fixtures?

Why do I want to use it instead of a normal fixture?

A fixture will be used like this:

def test_my(singleton):
...

And declared like this (in conftest.py):

@pytest.fixture(scope="module")
def singleton():
gloabl SINGLETON # created by other conftest.py parts
return SINGLETON

Generally, this is the Pytest way to do things.

It has one problem: you can’t use it as a parameter to the fixture.

Let’s say I want this:

@pytest.mark.parametrize("foo", singleton)
def test_is_good(foo):
assert foo.is_good()

You can’t. Fixtures are callable after test generations, and this is ground truth for Pytest. Nope, None. Not, No, Don’t.

With my precious you can (I can!):

from mytestlib import singleton

@pytest.mark.parametrize("foo", singleton)
def test_is_good(foo):
assert foo.is_good()

All you have to do is to fill it with content inside of conftest.py, which is usually done in pytest_configure function.

Conclusion

Do I need a conclusion? This trick is awesome.

--

--

George Shuklin
OpsOps

I work at Servers.com, most of my stories are about Ansible, Ceph, Python, Openstack and Linux. My hobby is Rust.