Mathew K Analytics

Lesson 7 · Pytest deep dive

pytest Tutorial #7: conftest.py & Sharing Fixtures

On testing real Python code with pytest. conftest.py: real shared fixtures, no imports required, plus autouse. Let's get into it. conftest.py is a special…

⬇ Download notebook

📓 Full notebook

Download .ipynb

pytest Deep-Dive, Video 7: conftest.py and Fixture Sharing#

  • Video seven of an eighteen-part series on testing real Python code with pytest.
  • conftest.py: real shared fixtures, no imports required, plus autouse.
  • Let's get into it.

Part 1: A New Helper - Running pytest Against Multiple Files#

import pytest, tempfile, os
def run_pytest_dir(files, args=None):
    tmpdir = tempfile.mkdtemp()
    for filename, content in files.items():
        with open(os.path.join(tmpdir, filename), 'w') as f:
            f.write(content)
    result = pytest.main([tmpdir, *(args or [])])
    return result

Part 2: A Basic conftest.py Fixture#

conftest_code = """
import pytest

@pytest.fixture
def sample_numbers():
    return [3, 1, 4, 1, 5, 9]
"""
test_code = """
def test_sum(sample_numbers):
    assert sum(sample_numbers) == 23

def test_length(sample_numbers):
    assert len(sample_numbers) == 6
"""
exit_code = run_pytest_dir({'conftest.py': conftest_code, 'test_numbers.py': test_code}, ['-v', '--no-header'])
print(exit_code)
============================= test session starts =============================
collecting ... collected 2 items

..\..\..\Users\makmw\AppData\Local\Temp\tmpkrkowb1y\test_numbers.py::test_sum PASSED [ 50%]
..\..\..\Users\makmw\AppData\Local\Temp\tmpkrkowb1y\test_numbers.py::test_length PASSED [100%]

============================== 2 passed in 0.12s ==============================
0

Part 3: One conftest.py, Multiple Test Files#

conftest_code = """
import pytest

@pytest.fixture
def sample_numbers():
    return [3, 1, 4, 1, 5, 9]
"""
test_a_code = """
def test_max_value(sample_numbers):
    assert max(sample_numbers) == 9
"""
test_b_code = """
def test_min_value(sample_numbers):
    assert min(sample_numbers) == 1
"""
files = {
    'conftest.py': conftest_code,
    'test_a.py': test_a_code,
    'test_b.py': test_b_code,
}
exit_code = run_pytest_dir(files, ['-v', '--no-header'])
print(exit_code)
============================= test session starts =============================
collecting ... collected 2 items

..\..\..\Users\makmw\AppData\Local\Temp\tmpo4i8dpg9\test_a.py::test_max_value PASSED [ 50%]
..\..\..\Users\makmw\AppData\Local\Temp\tmpo4i8dpg9\test_b.py::test_min_value PASSED [100%]

============================== 2 passed in 0.03s ==============================
0

Part 4: autouse - Fixtures That Run Without Being Requested#

conftest_code = """
import pytest

events = []

@pytest.fixture(autouse=True)
def track_every_test():
    events.append('setup')
    yield
    events.append('teardown')
"""
test_code = """
from conftest import events

def test_one():
    assert 'setup' in events

def test_two():
    assert events.count('setup') >= 2
"""
exit_code = run_pytest_dir({'conftest.py': conftest_code, 'test_autouse.py': test_code}, ['-v', '--no-header'])
print(exit_code)
============================= test session starts =============================
collecting ... collected 2 items

..\..\..\Users\makmw\AppData\Local\Temp\tmpshf_8cgw\test_autouse.py::test_one PASSED [ 50%]
..\..\..\Users\makmw\AppData\Local\Temp\tmpshf_8cgw\test_autouse.py::test_two PASSED [100%]

============================== 2 passed in 0.02s ==============================
0

Part 5: A Local Fixture Overrides a conftest.py Fixture#

conftest_code = """
import pytest

@pytest.fixture
def environment_name():
    return 'production'
"""
test_shared_code = """
def test_uses_shared_environment(environment_name):
    assert environment_name == 'production'
"""
test_override_code = """
import pytest

@pytest.fixture
def environment_name():
    return 'testing'

def test_uses_overridden_environment(environment_name):
    assert environment_name == 'testing'
"""
files = {
    'conftest.py': conftest_code,
    'test_shared.py': test_shared_code,
    'test_override.py': test_override_code,
}
exit_code = run_pytest_dir(files, ['-v', '--no-header'])
print(exit_code)
============================= test session starts =============================
collecting ... collected 2 items

..\..\..\Users\makmw\AppData\Local\Temp\tmpzxq24ue8\test_override.py::test_uses_overridden_environment PASSED [ 50%]
..\..\..\Users\makmw\AppData\Local\Temp\tmpzxq24ue8\test_shared.py::test_uses_shared_environment PASSED [100%]

============================== 2 passed in 0.03s ==============================
0

Part 6: A Real Pattern - a Shared Fake-Client Fixture#

conftest_code = """
import pytest

class FakeApiClient:
    def __init__(self):
        self.calls = []

    def get(self, path):
        self.calls.append(path)
        return {'path': path, 'status': 'ok'}

@pytest.fixture
def api_client():
    return FakeApiClient()
"""
test_users_code = """
def test_get_users(api_client):
    response = api_client.get('/users')
    assert response['status'] == 'ok'
    assert api_client.calls == ['/users']
"""
test_orders_code = """
def test_get_orders(api_client):
    response = api_client.get('/orders')
    assert response['path'] == '/orders'
"""
files = {
    'conftest.py': conftest_code,
    'test_users.py': test_users_code,
    'test_orders.py': test_orders_code,
}
exit_code = run_pytest_dir(files, ['-v', '--no-header'])
print(exit_code)
============================= test session starts =============================
collecting ... collected 2 items

..\..\..\Users\makmw\AppData\Local\Temp\tmpe25czxxr\test_orders.py::test_get_orders PASSED [ 50%]
..\..\..\Users\makmw\AppData\Local\Temp\tmpe25czxxr\test_users.py::test_get_users PASSED [100%]

============================== 2 passed in 0.03s ==============================
0

Wrap-Up: What You Learned#

  • conftest.py is a special filename pytest automatically discovers; its fixtures need no import at all.
  • One conftest.py fixture is available to every test file in the same directory, and any subdirectory.
  • @pytest.fixture(autouse=True) runs a fixture for every test in scope without any test requesting it.
  • A test file can define a fixture with the same name as one in conftest.py, overriding it locally.
  • Overriding only affects the file that redefines it; every other file still uses the shared conftest version.
  • A shared fake-client fixture in conftest.py avoids redefining the same setup across many test files.
  • That wraps up conftest.py and fixture sharing. Next up: Fixture Scopes and Finalization.

Found this useful?

All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.