The Marauder's Map must be activated ("I solemnly swear that I am up to no good") and deactivated ("Mischief managed!"). Yield fixtures follow the same pattern: setup β yield β teardown.
import pytest
class MaraudersMap:
def __init__(self):
self.is_active = False
self.tracking = []
def activate(self):
self.is_active = True
print("πΊοΈ I solemnly swear that I am up to no good!")
def deactivate(self):
self.is_active = False
self.tracking.clear()
print("πΊοΈ Mischief managed!")
def locate(self, person: str) -> str:
if not self.is_active:
raise RuntimeError("Map is not active!")
location = f"{person} β Great Hall"
self.tracking.append(location)
return location
@pytest.fixture
def marauders_map():
"""Yield fixture: setup before yield, teardown after."""
the_map = MaraudersMap()
the_map.activate() # β SETUP (before yield)
yield the_map # β test receives this object
the_map.deactivate() # β TEARDOWN (runs even if test fails!)
def test_locate_snape(marauders_map):
location = marauders_map.locate("Severus Snape")
assert "Snape" in location
assert marauders_map.is_active is True
def test_locate_draco(marauders_map):
location = marauders_map.locate("Draco Malfoy")
assert "Draco" in location
The teardown code after yield always runs, even if the test raises an exception β like a magical contract!
An alternative to yield for teardown. Useful when you need multiple cleanup steps or conditional teardown (like destroying multiple Horcruxes).
@pytest.fixture
def horcrux_collection(request):
"""Using addfinalizer β can register multiple teardown steps."""
horcruxes = []
def destroy_all_horcruxes():
for h in horcruxes:
print(f"π Destroying horcrux: {h}")
horcruxes.clear()
def notify_dumbledore():
print("π¨ Notifying Dumbledore: all horcruxes destroyed.")
request.addfinalizer(notify_dumbledore) # runs LAST (LIFO order)
request.addfinalizer(destroy_all_horcruxes) # runs FIRST
# Setup
horcruxes.extend(["Diary", "Ring", "Locket", "Cup", "Diadem", "Harry", "Nagini"])
return horcruxes
def test_count_horcruxes(horcrux_collection):
assert len(horcrux_collection) == 7
Like the Trace on underage wizards, autouse=True fixtures apply to every test in their scope without being explicitly requested.
@pytest.fixture(autouse=True)
def reset_house_points():
"""Automatically resets house points before EVERY test."""
Hogwarts.reset_points()
yield
# After each test, verify no house has negative points
for house in ["Gryffindor", "Slytherin", "Hufflepuff", "Ravenclaw"]:
assert Hogwarts.get_points(house) >= 0
def test_award_points():
Hogwarts.award("Gryffindor", 50)
assert Hogwarts.get_points("Gryffindor") == 50
def test_deduct_points():
Hogwarts.deduct("Slytherin", 10)
# autouse ensures points were reset, so this starts from 0
# Gryffindor is back to 0, not 50!
assert Hogwarts.get_points("Gryffindor") == 0
@pytest.fixture(params=["Polyjuice", "Veritaserum", "Amortentia"])
def potion(request):
"""This fixture runs the test 3 times β once per potion."""
potion_name = request.param
cauldron = Cauldron()
brewed = cauldron.brew(potion_name)
yield brewed
cauldron.clean() # teardown
def test_potion_is_liquid(potion):
"""Runs 3 times: Polyjuice, Veritaserum, Amortentia."""
assert potion.state == "liquid"
def test_potion_has_color(potion):
"""Also runs 3 times! 3 potions Γ 2 tests = 6 total test cases."""
assert potion.color is not None
Sometimes you want to parametrize the fixture itself from the test, passing arguments through the fixture (like casting a spell through another wizard's wand).
@pytest.fixture
def wand(request):
"""Fixture receives params indirectly from @pytest.mark.parametrize."""
wood, core = request.param
return Wand(wood=wood, core=core, length_inches=11.0)
@pytest.mark.parametrize("wand", [
("holly", "phoenix_feather"),
("elder", "thestral_tail_hair"),
("vine", "dragon_heartstring"),
], indirect=True) # β key: tells pytest to pass params TO the fixture
def test_wand_allegiance(wand):
"""Each parametrized tuple is passed to the 'wand' fixture via request.param."""
assert wand.wood in ["holly", "elder", "vine"]
assert wand.cast("Lumos") == "Lumos!"
The indirect=True flag redirects parametrize values into request.param inside the fixture, letting you build complex objects from simple test-level parameters.