pytest Fixtures
pytest Fixtures
Section titled โpytest FixturesโWhat it is
Section titled โWhat it isโA pytest fixture is a function decorated with @pytest.fixture that provides setup (and optional teardown) for tests. Any test function that names the fixture as a parameter automatically receives whatever the fixture returns โ pytest resolves and calls fixtures for you based on the parameter name, no manual setup/teardown calls needed in each test.
Before this feature
Section titled โBefore this featureโWithout fixtures, shared setup gets copy-pasted into every test, or bundled into a setUp/tearDown pair that runs for every test in a class whether it needs that setup or not:
import unittest
class TestOrders(unittest.TestCase): def setUp(self): self.db = connect_to_test_db() # runs before EVERY test method
def tearDown(self): self.db.close()
def test_create_order(self): order = self.db.create_order(...) assert order.id is not NoneAfter this feature
Section titled โAfter this featureโimport pytest
@pytest.fixturedef db(): connection = connect_to_test_db() yield connection # this is what gets injected into the test connection.close() # runs after the test โ teardown
def test_create_order(db): # pytest sees the 'db' parameter, resolves the fixture order = db.create_order(...) assert order.id is not None
def test_something_else(): # doesn't need 'db' at all โ doesn't pay for that setup assert 1 + 1 == 2Why this is better
Section titled โWhy this is betterโ- Opt-in per test: only tests that actually declare a fixture as a parameter run its setup โ unlike class-wide
setUp, unrelated tests arenโt slowed down by setup they donโt need. - Composable: fixtures can depend on other fixtures (just by naming them as parameters), building up shared setup in layers instead of one monolithic
setUp. yieldgives clean teardown: code afteryieldruns after the test finishes (pass or fail), the same guarantee afinallyblock gives, without writingtry/finallyby hand in every test.
Key notes / edge cases
Section titled โKey notes / edge casesโ- Fixture scope controls how often itโs recreated:
@pytest.fixture(scope="function")(default โ once per test),"module"(once per file),"session"(once for the whole test run) โ useful for expensive setup (e.g. a real database connection) that doesnโt need to be rebuilt for every single test. - Fixtures are matched to tests purely by parameter name โ
def test_x(db):works because a fixture literally nameddbexists and is visible (defined in the same file or in aconftest.py). conftest.pyis pytestโs mechanism for sharing fixtures across multiple test files without importing them explicitly โ any fixture defined there is automatically available to tests in that directory and below.- A fixture using
returninstead ofyieldhas no teardown phase โ useyieldspecifically when thereโs cleanup to run after the test.
@pytest.fixture(scope="session")def expensive_resource(): resource = build_expensive_thing() # built once for the entire test run yield resource resource.cleanup()Quick practice
Section titled โQuick practiceโ-
How does pytest know to inject a fixture into a specific test function?
Answer
By matching the test function's parameter name to a fixture of the same name โ `def test_x(db):` pulls in the fixture named `db`. -
Whatโs the difference between a fixture that
returns a value and one thatyields it?Answer
`yield` allows code after it to run as teardown once the test finishes; `return` provides the value with no teardown phase. -
Why would you set a fixtureโs scope to
"session"instead of the default?Answer
To avoid recreating expensive setup (like a real database connection) for every single test โ a session-scoped fixture is created once and reused across the entire test run.