The Room of Requirement: Fixtures Basics


pytest Deep-Dives — Hogwarts Edition

🚪 What Are Fixtures? — The Room That Provides What You Need

The Room of Requirement at Hogwarts manifests whatever the seeker truly needs. pytest fixtures work the same way: they set up and provide objects that your tests require — wands, wizards, potions, database connections — anything.

A fixture is simply a function decorated with @pytest.fixture that returns (or yields) an object your test can use.

Basic Fixture — Summoning a Wand

import pytest

class Wand:
    def __init__(self, wood: str, core: str, length_inches: float):
        self.wood = wood
        self.core = core
        self.length_inches = length_inches

    def cast(self, spell: str) -> str:
        return f"{spell}!"

@pytest.fixture
def elder_wand():
    """The Deathstick, the Wand of Destiny."""
    return Wand(wood="elder", core="thestral_tail_hair", length_inches=15.0)

def test_elder_wand_properties(elder_wand):
    assert elder_wand.wood == "elder"
    assert elder_wand.core == "thestral_tail_hair"

def test_elder_wand_casts_spell(elder_wand):
    result = elder_wand.cast("Expelliarmus")
    assert result == "Expelliarmus!"

pytest sees elder_wand in the test signature, finds the matching fixture, calls it, and injects the result. No manual setup needed!

šŸ§™ Fixtures Using Other Fixtures — Composition (The Order of the Phoenix)

class Wizard:
    def __init__(self, name: str, house: str, wand: Wand):
        self.name = name
        self.house = house
        self.wand = wand
        self.spells_cast = []

    def cast(self, spell: str) -> str:
        result = self.wand.cast(spell)
        self.spells_cast.append(spell)
        return result

@pytest.fixture
def harry_wand():
    return Wand(wood="holly", core="phoenix_feather", length_inches=11.0)

@pytest.fixture
def harry_potter(harry_wand):
    """Fixtures can depend on other fixtures — like The Chosen One needs his wand."""
    return Wizard(name="Harry Potter", house="Gryffindor", wand=harry_wand)

def test_harry_can_cast_patronus(harry_potter):
    result = harry_potter.cast("Expecto Patronum")
    assert result == "Expecto Patronum!"
    assert "Expecto Patronum" in harry_potter.spells_cast

šŸ° Fixture Scope — How Long the Room Stays Open

By default, fixtures are function-scoped: created fresh for each test. But some resources are expensive (like brewing Polyjuice Potion — it takes a month!). Use scope to control lifetime:

class PotionsCauldron:
    """Expensive to set up — like Snape's classroom."""
    brew_count = 0

    def __init__(self):
        PotionsCauldron.brew_count += 1
        self.contents = None

    def brew(self, potion_name: str):
        self.contents = potion_name
        return self.contents

# function scope (default): new cauldron per test
@pytest.fixture(scope="function")
def disposable_cauldron():
    return PotionsCauldron()

# session scope: ONE cauldron for the ENTIRE test session
@pytest.fixture(scope="session")
def shared_cauldron():
    """Like the Room of Requirement — created once, used by everyone."""
    return PotionsCauldron()

# module scope: one per test file
@pytest.fixture(scope="module")
def module_cauldron():
    return PotionsCauldron()

# class scope: one per test class
@pytest.fixture(scope="class")
def class_cauldron():
    return PotionsCauldron()

def test_brew_polyjuice(shared_cauldron):
    assert shared_cauldron.brew("Polyjuice") == "Polyjuice"

def test_brew_veritaserum(shared_cauldron):
    # Same cauldron instance as above — no re-creation!
    assert shared_cauldron.brew("Veritaserum") == "Veritaserum"
ScopeCreatedDestroyedUse Case
functionBefore each testAfter each testCheap, isolated objects
classOnce per test classAfter class finishesShared within a class
moduleOnce per .py fileAfter file finishesDB connections per file
sessionOnce per test runAfter all testsExpensive shared resources

šŸ“œ usefixtures — Applying Without Requesting the Return Value

@pytest.fixture
def silence_classroom():
    """Cast Silencio on the classroom — no return value needed."""
    print("\nšŸ”‡ Silencio! Classroom is quiet.")
    # setup happens just by running the fixture

@pytest.mark.usefixtures("silence_classroom")
class TestPotionsExam:
    """All tests in this class get a silent classroom, 
    but don't need the fixture's return value."""

    def test_brew_sleeping_draught(self):
        potion = brew("Sleeping Draught")
        assert potion.effect == "sleep"

    def test_brew_antidote(self):
        potion = brew("Antidote to Common Poisons")
        assert potion.cures == "common_poisons"

@pytest.mark.usefixtures triggers the fixture without injecting its return value into the test. Perfect for side-effect-only fixtures (logging, DB seeding, environment setup).