Pytest conftest.py: Fixture Sharing and Hooks
In Python testing with pytest, the
conftest.py file serves as a decentralized configuration
hub that provides reusable fixtures and custom hook implementations
across test suites without requiring explicit imports. This article
explores how pytest leverages the directory hierarchy to
control the scope and visibility of fixtures, how nested
conftest.py files override or extend parent behaviors, and
how directory-specific hooks dynamically customize test execution and
reporting.
Hierarchical Fixture Discovery
The primary feature of conftest.py is its ability to
share fixtures across multiple test modules automatically. When a test
function requests a fixture, pytest traverses the directory
tree upward, starting from the directory containing the test file up to
the root directory where the test runner was executed.
This discovery model eliminates the need for
from conftest import my_fixture statements. Because
pytest dynamically registers fixtures during its discovery
phase, any fixture defined in a conftest.py file is
immediately available to:
- All test files located in the same directory as that
conftest.py. - All test files in any subdirectories underneath it.
Conversely, tests located in sibling or parent directories cannot
access fixtures defined inside an isolated child directory's
conftest.py. This mechanism ensures strict isolation
between disparate test suites, such as unit tests and end-to-end
integration tests.
Directory-Scoped Fixture Overriding
As test suites grow, different modules often require variations of
the same underlying resource—for example, a database connection with
different permissions or mocked network clients. pytest
allows downstream directories to override fixtures declared in parent
directories.
When a child directory contains its own conftest.py that
defines a fixture with the same name as one in a parent
conftest.py, the child fixture shadows the parent fixture
for all tests within that child directory tree. This enables developers
to declare generic, global defaults at the project root while
specializing behaviors in subfolders without renaming fixtures or
rewriting test signatures.
Interaction Between Fixture Scopes and Directory Boundaries
Fixtures specify their lifespan using the scope
argument: function, class,
module, package, or session. The
location of the conftest.py file introduces an additional
spatial boundary that works alongside temporal fixture scopes:
- Global Access: A
session-scoped fixture in the rootconftest.pyexecutes once across the entire test session. - Localized Session Scope: A
session-scoped fixture defined in a sub-folder'sconftest.pystill runs once, but it is only instantiated if and when tests within that specific sub-folder run. It remains inaccessible to tests outside that folder hierarchy.
This synergy allows expensive operations (like starting an external Docker container) to be confined strictly to the sub-suite that requires them, avoiding unneeded resource consumption when running unrelated tests.
Hook Configurations and Local Customization
pytest operates via a hook-based plugin architecture.
Hooks allow users to intercept and modify the testing lifecycle,
including test collection, environment configuration, reporting, and
execution phases.
Every conftest.py functions as an in-tree plugin. By
implementing standard pytest_* hooks within a
conftest.py, you configure behaviors specifically for that
directory's scope:
pytest_collection_modifyitems: Alter, filter, or reorder test items dynamically (e.g., adding marks like@pytest.mark.integrationto all tests within a specific directory).pytest_runtest_setup: Run custom setup logic before individual tests execute within that specific folder.pytest_generate_tests: Implement dynamic parameterization tailored to the resources of that directory.
While hooks defined in the root conftest.py apply
globally to the entire test run, hooks implemented in nested
conftest.py files only intercept events related to tests
collected within that folder's branch, providing fine-grained control
over execution mechanics without polluting the global testing
context.