pythonpytestfixturesconftestpytest-fixtures

What is conftest.py for in Pytest?


I'm trying to understand what conftest.py files are meant to be used for.

In my (currently small) test suite I have one conftest.py file at the project root. I use it to define the fixtures that I inject into my tests.

I have two questions:

  1. Is this the correct use of conftest.py? Does it have other uses?
  2. Can I have more than one conftest.py file? When would I want to do that? Examples will be appreciated.

More generally, how would you define the purpose and correct use of conftest.py file(s) in a pytest test suite?


Solution

  • Is this the correct use of conftest.py?

    Yes it is. Fixtures are a potential and common use of conftest.py. The fixtures that you will define will be shared among all tests in your test suite. However, defining fixtures in the root conftest.py might be useless and it would slow down testing if such fixtures are not used by all tests.

    Does it have other uses?

    Yes it does.

    Can I have more than one conftest.py file?

    Yes you can and it is strongly recommended if your test structure is somewhat complex. conftest.py files have directory scope. Therefore, creating targeted fixtures and helpers is good practice.

    When would I want to do that? Examples will be appreciated.

    Several cases could fit:

    Creating a set of tools or hooks for a particular group of tests.

    root/mod/conftest.py

    def pytest_runtest_setup(item):
        print("I am mod")
        #do some stuff
    
    
    test root/mod2/test.py will NOT produce "I am mod"
    

    Loading a set of fixtures for some tests but not for others.

    root/mod/conftest.py

    @pytest.fixture()
    def fixture():
        return "some stuff"
    

    root/mod2/conftest.py

    @pytest.fixture()
    def fixture():
        return "some other stuff"
    

    root/mod2/test.py

    def test(fixture):
        print(fixture)
    

    Will print "some other stuff".

    Overriding hooks inherited from the root conftest.py.

    root/mod/conftest.py

    def pytest_runtest_setup(item):
        print("I am mod")
        #do some stuff
    

    root/conftest.py

    def pytest_runtest_setup(item):
        print("I am root")
        #do some stuff
    

    By running any test inside root/mod, only "I am mod" is printed.

    You can read more about conftest.py here.

    EDIT:

    What if I need plain-old helper functions to be called from a number of tests in different modules - will they be available to me if I put them in a conftest.py? Or should I simply put them in a helpers.py module and import and use it in my test modules?

    You can use conftest.py to define your helpers. However, you should follow common practice. Helpers can be used as fixtures at least in pytest. For example in my tests I have a mock redis helper which I inject into my tests this way.

    root/helper/redis/redis.py

    @pytest.fixture
    def mock_redis():
        return MockRedis()
    

    root/tests/stuff/conftest.py

    pytest_plugin="helper.redis.redis"
    

    root/tests/stuff/test.py

    def test(mock_redis):
        print(mock_redis.get('stuff'))
    

    This will be a test module that you can freely import in your tests. NOTE that you could potentially name redis.py as conftest.py if your module redis contains more tests. However, that practice is discouraged because of ambiguity.

    If you want to use conftest.py, you can simply put that helper in your root conftest.py and inject it when needed.

    root/tests/conftest.py

    @pytest.fixture
    def mock_redis():
        return MockRedis()
    

    root/tests/stuff/test.py

    def test(mock_redis):
        print(mock_redis.get(stuff))
    

    Another thing you can do is to write an installable plugin. In that case your helper can be written anywhere but it needs to define an entry point to be installed in your and other potential test frameworks. See this.

    If you don't want to use fixtures, you could of course define a simple helper and just use the plain old import wherever it is needed.

    root/tests/helper/redis.py

    class MockRedis():
        # stuff
    

    root/tests/stuff/test.py

    from helper.redis import MockRedis
    
    def test():
        print(MockRedis().get(stuff))
    

    However, here you might have problems with the path since the module is not in a child folder of the test. You should be able to overcome this (not tested) by adding an __init__.py to your helper

    root/tests/helper/init.py

    from .redis import MockRedis
    

    Or simply adding the helper module to your PYTHONPATH.