Python interview question
How do pytest fixtures work? Explain scope, yield teardown and conftest.py.
Short answer
Fixtures are functions decorated with @pytest.fixture that pytest injects into tests by parameter name. Scope (function, class, module, package, session) controls how often they are created. Code after yield is teardown. conftest.py shares fixtures across a directory without imports.
Answer
A fixture is a function decorated with @pytest.fixture that provides something a test needs: a browser, an API client, a database row, a temporary directory. A test requests a fixture simply by naming it as a parameter, and pytest builds it (and any fixtures it depends on), passes it in, and tears it down afterwards. This replaces setUp/tearDown with explicit, composable dependencies.
Basic fixture with teardown
import pytest
import requests
@pytest.fixture
def api():
session = requests.Session()
session.headers["Authorization"] = "Bearer test-token"
yield session # value given to the test
session.close() # teardown, runs even if the test fails
def test_list_orders(api):
res = api.get("https://api.example.com/orders", timeout=10)
assert res.status_code == 200
Code before yield is setup; code after is teardown. If setup raises before reaching yield, the teardown part does not run, so keep setup that can fail small, or use request.addfinalizer to register cleanup step by step.
Scope
@pytest.fixture(scope="session")
def base_url():
return "https://staging.example.com"
| Scope | Created once per |
|---|---|
function (default) |
test |
class |
test class |
module |
test file |
package |
package (directory with tests) |
session |
whole test run (per worker with xdist) |
Wider scope is faster but shares state: a session-scoped user that one test modifies can break another. A fixture can only depend on fixtures of the same or wider scope; requesting a function-scoped fixture from a session-scoped one raises ScopeMismatch.
conftest.py
Fixtures defined in a conftest.py are available to every test in that directory and below, with no import. Typical layout:
tests/
conftest.py # session-wide: config, base_url, api client
api/
conftest.py # API-specific fixtures
test_orders.py
ui/
conftest.py # browser/page fixtures
A fixture in a deeper conftest.py (or in the test module) overrides one with the same name higher up.
Composition and factories
Fixtures can request other fixtures, which builds a dependency graph:
@pytest.fixture
def make_user(api, base_url):
created = []
def _make(role="member"):
res = api.post(f"{base_url}/users", json={"role": role}, timeout=10)
res.raise_for_status()
created.append(res.json()["id"])
return res.json()
yield _make
for user_id in created:
api.delete(f"{base_url}/users/{user_id}", timeout=10)
def test_admin_can_see_reports(make_user):
admin = make_user(role="admin")
...
The factory fixture pattern lets a test create as many objects as it needs while still guaranteeing cleanup.
Other useful features
autouse=Trueapplies a fixture to every test in scope (use sparingly, it hides dependencies).params=[...]on a fixture runs each dependent test once per value;request.paramgives the current one.- Built-ins:
tmp_path,monkeypatch,capsys,caplog,request. pytest --fixtureslists available fixtures;pytest --setup-showshows setup and teardown order.
What interviewers look for
- Injection by name and
yieldteardown. - Scope trade-offs, including shared-state risk and
ScopeMismatch. conftest.pydiscovery and overriding.- Factory fixtures or parametrised fixtures for real-world data setup.
A strong answer covers
- Dependency injection by argument name
- Scopes: function, class, module, package, session
- yield for setup and teardown
- conftest.py shares fixtures without imports
- Mentions fixture composition, parametrize, autouse or factory fixtures