45

I've got a large conftest.py file that I wish to split into smaller parts, for two reasons:

  1. The file is very large (~1000 lines, including documentation)
  2. Some of the fixtures depend on other fixtures, and I have no reason to expose those other fixtures as part of the conftest "API" when users look for relevant fixtures

I am not aware of any mechanism provided by pytest to resolve conftest files in multiple locations within the same folder, so I contrived one, below:

import sys
import os


sys.path.append(os.path.dirname(__file__))


from _conftest_private_part_1 import *
from _conftest_private_part_2 import *
from _conftest_private_part_3 import *


@pytest.fixture
def a_fixture_that_is_part_of_the_public_conftest_api():
    pass

This works for my needs, but I do wonder if there is a better way.

alkalinity
  • 1,750
  • 2
  • 21
  • 32

4 Answers4

59

You can put your stuff in other modules and reference them using a pytest_plugins variable in your conftest.py:

pytest_plugins = ['module1', 'module2']

This will also work if your conftest.py has hooks on them.

Bruno Oliveira
  • 13,694
  • 5
  • 43
  • 41
  • 3
    Can you show example code please? – Gulzar Nov 20 '22 at 16:28
  • 5
    As of 2022, Defining 'pytest_plugins' in a non-top-level conftest is no longer supported: It affects the entire test suite instead of just below the conftest as expected. – DPGraham4401 Jan 03 '23 at 17:23
  • @DPGraham4401 Would you be able to detail and/or link to the suggested solution? – J.C. Jul 14 '23 at 15:26
  • Yes, [here under the deprecations and removal section for v 4.0](https://docs.pytest.org/en/7.1.x/deprecations.html#:~:text=pytest_plugins%20in%20non%2Dtop%2Dlevel%20conftest%20files,-Removed%20in%20version&text=Defining%20pytest_plugins%20is%20now%20deprecated,tests%20at%20or%20below%20it.) – DPGraham4401 Jul 15 '23 at 17:47
6

You shouldn't need any fancy magic for that. py.test automatically adds the path of the current test file to sys.path, as well as all parent paths up to the directory it was targeted at.

Because of that, you don't even need to put that shared code into a conftest.py. You can just put into plain modules or packages and then import it (if you want to share fixtures, those have to be in a conftest.py).

Also, there is this note about importing from conftest.py in the documentation:

If you have conftest.py files which do not reside in a python package directory (i.e. one containing an __init__.py) then “import conftest” can be ambiguous because there might be other conftest.py files as well on your PYTHONPATH or sys.path. It is thus good practise for projects to either put conftest.py under a package scope or to never import anything from a conftest.py file.

Björn Pollex
  • 75,346
  • 28
  • 201
  • 283
  • 2
    I had wanted to be able to use the fixtures without them being in the "main" conftest.py file. Glad to know about the path though. – alkalinity Nov 21 '14 at 21:05
3

Alternativelly, you could try:

In my_fixtures.py:

@pytest.fixture
def some_fixture():
  yield

In conftest.py:

import my_fixtures


# Adding the fixture to attributes of `conftest` will register it
some_fixture = my_fixtures.some_fixture

It seems that pytest detect fixture by iterating over conftest attributes and checking for some attr._pytestfixturefunction added by @pytest.fixture.

So as long as conftest.py contains fixture attributes, it doesn't really matter which file is the fixture defined.

I haven't tried it but I would expect the following to work too:

# In `conftest.py`
from my_fixtures import *
Conchylicultor
  • 4,631
  • 2
  • 37
  • 40
0

This works for me and seems easier/clearer:

Top level tests/conftest.py (example of re-usable print debug of requests.Response):

import pytest
import requests
from requests_toolbelt.utils import dump


@pytest.fixture(scope="session")
def print_response(response: requests.Response):
    data = dump.dump_all(response)
    print("========================")
    print(data.decode('utf-8'))
    print("========================")

    print("response.url = {}".format(response.url))
    print("response.request = {}".format(response.request))
    print("response.status_code = {}".format(response.status_code))
    print("response.headers['content-type'] = {}".format(response.headers['content-type']))
    print("response.encoding = {}".format(response.encoding))
    try:
        print("response.json = {}".format(response.json()))
    except Exception:
        print("response.text = {}".format(response.text))
    print("response.end")

From lower level conftest import the higher level conftest code - e.g., tests/package1/conftest.py:

from tests.conftest import *

Then, in your lower level tests within tests/package1/test_*.py, you simply import via:

from tests.package1 import conftest

And then you have the merged configtests from one conftest available. Repeat this pattern for your other lower level detailed/modular conftest.py files throughout the tests hierarchy.

J.D.
  • 595
  • 1
  • 9
  • 16