Pytest Fixtures
Easily reuse them
I've always like the idea of fixtures in
However, I've encountered friction with them in the past, primarily because I'd need to slightly modify the fixture for each test. I needed a better way to not only override the fixture itself but also any other fixtures that it depended on.
Created: 2024-09-13
Conftest.py
import getpass
from typing import NamedTuple
import pytest
class User(NamedTuple):
username: str
locale: str
@pytest.fixture()
def username(request):
return getattr(request, "param", f"{getpass.getuser()}@email.com")
@pytest.fixture()
def default_username(username):
return username
@pytest.fixture()
def admin_username():
return "admin@email.com"
@pytest.fixture()
def locale(request):
return getattr(request, "param", "en")
@pytest.fixture()
def default_locale(locale):
return locale
@pytest.fixture()
def user(request, username, locale):
return getattr(request, "param", User(username=username, locale=locale))
test_users.py
import pytest
def test_default_user(user):
assert '@email.com' in user.username
assert user.locale == 'en'
@pytest.mark.parametrize('username', ['test_user@email.com'], indirect=True)
def test_custom_username(user):
assert user.username == 'test_user@email.com'
assert user.locale == 'en'
@pytest.mark.parametrize('locale', ['fr'], indirect=True)
def test_custom_locale(user):
assert '@email.com' in user.username
assert user.locale == 'fr'
@pytest.mark.parametrize('user', [User('custom@email.com', 'de')], indirect=True)
def test_fully_custom_user(user):
assert user.locale == 'de'
assert user.username == 'custom@email.com'
@pytest.mark.parametrize('username, locale', [('test@email.com', 'es')], indirect=True)
def test_custom_username_and_locale(user):
assert user.username == 'test@email.com'
assert user.locale == 'es'
# Alternatively, you allow a itertools.product like operation
@pytest.mark.parametrize('username', ['test@email.com'], indirect=True)
@pytest.mark.parametrize('locale', ['es'], indirect=True)
def test_custom_username_and_locale_alt(user):
assert user.username == 'test@email.com'
assert user.locale == 'es'
More Advanced
partial
like behavior for fixtures. Typically this works a dependency chain. However, you can create a fixture that allows you to call another fixture with a predefined value.
import getpass
from typing import NamedTuple
import pytest
class User(NamedTuple):
username: str
locale: str
@pytest.fixture()
def username(monkeypatch, request):
from someapp import getuser
username = getattr(request, "param", f"{getpass.getuser()}@email.com")
def override_user():
return username
monkeypatch.setattr(getuser, override_user)
return username
@pytest.fixture()
def default_username(username):
return username
@pytest.fixture()
def admin_username(request):
return request.getfixturevalue('username', 'admin@email.com')
@pytest.fixture()
def locale(request):
return getattr(request, "param", "en")
@pytest.fixture()
def default_locale(locale):
return locale
@pytest.fixture()
def user(request, username, locale):
return getattr(request, "param", User(username=username, locale=locale))