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.
Fixtures: Define fixtures for static data used by tests. This data can be accessed by all tests in the suite unless specified otherwise. This could be data as well as helpers of modules which will be passed to all tests.
External plugin loading:
conftest.pyis used to import external plugins or modules. By defining the following global variable, pytest will load the module and make it available for its test. Plugins are generally files defined in your project or other modules which might be needed in your tests. You can also load a set of predefined plugins as explained here.pytest_plugins = "someapp.someplugin"Hooks: You can specify hooks such as setup and teardown methods and much more to improve your tests. For a set of available hooks, read Hooks link. Example:
def pytest_runtest_setup(item): """ called before ``pytest_runtest_call(item). """ #do some stuff`Test root path: This is a bit of a hidden feature. By defining
conftest.pyin your root path, you will havepytestrecognizing your application modules without specifyingPYTHONPATH. In the background, py.test modifies yoursys.pathby including all submodules which are found from the root path.
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.
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.
Fixtures: Define fixtures for static data used by tests. This data can be accessed by all tests in the suite unless specified otherwise. This could be data as well as helpers of modules which will be passed to all tests.
External plugin loading:
conftest.pyis used to import external plugins or modules. By defining the following global variable, pytest will load the module and make it available for its test. Plugins are generally files defined in your project or other modules which might be needed in your tests. You can also load a set of predefined plugins as explained here.pytest_plugins = "someapp.someplugin"Hooks: You can specify hooks such as setup and teardown methods and much more to improve your tests. For a set of available hooks, read Hooks link. Example:
def pytest_runtest_setup(item): """ called before ``pytest_runtest_call(item). """ #do some stuff`Test root path: This is a bit of a hidden feature. By defining
conftest.pyin your root path, you will havepytestrecognizing your application modules without specifyingPYTHONPATH. In the background, py.test modifies yoursys.pathby including all submodules which are found from the root path.
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.
Update Pytest 7.2 (2023 - March)
Adding this answer because there are some comment regarding missing information in the docs, which I guess was back when this question was first asked, but now is very well explained and documented.
From conftest.py: sharing fixtures across multiple files till the bottom of the page (and actually anything related to fixture or setting up pytest can be applied to the conftest.py) is related to conftest.py
Therefore, by quickly scanning the docs these are the main functionality (but not inclusive) :
- Serve fixtures to tests (no matter the fixture scope)
- Modularize the available fixture for each
conftest.pyin a Tree like hierarchy - Scope: sharing fixtures across classes, modules, packages or session
- Child
conftest.pycan override parentconftest.py(but not the opposite) See this - Serve fixtures from a
third-party plugins - Loading and Sharing test data across multiple tests
- Using fixtures from other projects
- Serve and share Hooks
Now on the accepted answer, it says Test root path: : This is a bit of a hidden feature. By defining ... but as for now, the best way to do this is instead add pythonpath to a pytest.ini on the root of your project (the actual project root, not project/tests ) see: pythonpath and does all for you and is more powerfull
Which looks like
[pytest]
pythonpath = src1 src2
Lastly, here I drop one of the many graphs that are in the pytest docs about The boundaries of the scopes can be visualized like this:

How pytest fixtures screwed me over
Specify conftest / setup file in the ini file
Videos
I need to write this of my chest, so to however wants to read this, here is my "fuck my life" moment as a python programmer for this week:
I am happily refactoring a bunch of pytest-testcases for a work project. With this, my team decided to switch to explicitly import fixtures into each test-file instead of relying on them "magically" existing everywhere. Sounds like a good plan, makes things more explicit and easier to understand for newcomers. Initial testing looks good, everything works.
I commit, the full testsuit runs over night. Next day I come back to most of the tests erroring out. Each one with a connection error. "But that's impossible?" We use a scope of session for your connection, there's only one connection for the whole testsuite run. There can be a couple of test running fine and than a bunch who get a connection error. How is the fixture re-connecting? I involve my team, nobody knows what the hecks going on here. So I start digging into it, pytests docs usually suggest to import once in the contest.py but there is nothing suggesting other imports should't work.
Than I get my Heureka: unter some obscure stack overflow post is a comment: pytest resolves fixtures by their full import path, not just the symbol used in the file. What?
But that's actually why non of the session-fixtures worked as expected. Each import statement creates a new fixture, each with a different import-path, even if they all look the same when used inside tests. Each one gets initialised seperatly and as they are scoped to the session, only destroyed at the end of the testsuite. Great... So back to global imports we went.
I hope this helps some other tormented should and shortens the search for why pytest fixtures sometimes don't work as expected. Keep Coding!