When a wizard shouts an incantation, capsys captures everything that goes to stdout and stderr โ like an Extendable Ear from Weasleys' Wizard Wheezes.
def cast_spell_aloud(spell_name: str):
"""A wizard who shouts their spells."""
print(f"๐ช {spell_name.upper()}!")
def test_incantation_is_shouted(capsys):
cast_spell_aloud("Expelliarmus")
captured = capsys.readouterr()
assert "EXPELLIARMUS" in captured.out
assert captured.err == "" # no errors
def test_multiple_spells(capsys):
cast_spell_aloud("Lumos")
cast_spell_aloud("Nox")
captured = capsys.readouterr()
assert "LUMOS" in captured.out
assert "NOX" in captured.out
While capsys captures Python-level output, capfd works at the file-descriptor level โ capturing output from C extensions and subprocesses too. Like intercepting owl post at the Ministry level.
import os
def test_low_level_howler(capfd):
"""capfd captures even os.write() calls."""
os.write(1, b"RONALD WEASLEY! HOW DARE YOU STEAL THAT CAR!\n")
captured = capfd.readouterr()
assert "RONALD WEASLEY" in captured.out
The Quill of Acceptance records every magical child born in Britain. caplog does the same for Python's logging module.
import logging
logger = logging.getLogger("hogwarts.admissions")
def register_student(name: str, is_magical: bool):
if is_magical:
logger.info(f"Quill recorded: {name}")
else:
logger.warning(f"Muggle detected: {name}")
def test_magical_student_logged(caplog):
with caplog.at_level(logging.INFO, logger="hogwarts.admissions"):
register_student("Harry Potter", is_magical=True)
assert "Quill recorded: Harry Potter" in caplog.text
assert len(caplog.records) == 1
assert caplog.records[0].levelname == "INFO"
def test_muggle_warning(caplog):
with caplog.at_level(logging.WARNING):
register_student("Dudley Dursley", is_magical=False)
assert "Muggle detected" in caplog.text
assert caplog.records[0].levelname == "WARNING"
Polyjuice Potion lets you temporarily become someone else. monkeypatch lets you temporarily replace attributes, environment variables, and dict items โ and it automatically reverts after the test!
import os
class OllivandersShop:
def get_wand_price(self) -> int:
base = int(os.environ.get("GALLEON_EXCHANGE_RATE", "7"))
return base * 2
def test_wand_price_with_custom_exchange(monkeypatch):
"""Temporarily change an environment variable."""
monkeypatch.setenv("GALLEON_EXCHANGE_RATE", "10")
shop = OllivandersShop()
assert shop.get_wand_price() == 20
def test_wand_price_default(monkeypatch):
"""Ensure env var doesn't exist (undo any previous state)."""
monkeypatch.delenv("GALLEON_EXCHANGE_RATE", raising=False)
shop = OllivandersShop()
assert shop.get_wand_price() == 14 # 7 * 2
class MinistryRegistry:
_minister = "Cornelius Fudge"
@classmethod
def get_minister(cls):
return cls._minister
def test_new_minister(monkeypatch):
"""Temporarily replace a class attribute."""
monkeypatch.setattr(MinistryRegistry, "_minister", "Kingsley Shacklebolt")
assert MinistryRegistry.get_minister() == "Kingsley Shacklebolt"
# After the test, it reverts to "Cornelius Fudge" automatically!
def test_patch_dict(monkeypatch):
"""Patch dictionary items."""
spell_registry = {"Lumos": "light", "Nox": "darkness"}
monkeypatch.setitem(spell_registry, "Lumos", "SUPER BRIGHT LIGHT")
assert spell_registry["Lumos"] == "SUPER BRIGHT LIGHT"
Like the Vanishing Cabinet that creates a passage and then disappears, tmp_path provides a temporary directory unique to each test โ automatically cleaned up.
from pathlib import Path
def save_spell_book(directory: Path, spells: list[str]):
spell_file = directory / "advanced_potions.txt"
spell_file.write_text("\n".join(spells))
return spell_file
def test_save_spell_book(tmp_path):
"""tmp_path is a unique Path object per test invocation."""
spells = ["Sectumsempra", "Levicorpus", "Liberacorpus"]
result = save_spell_book(tmp_path, spells)
assert result.exists()
content = result.read_text()
assert "Sectumsempra" in content
assert len(content.splitlines()) == 3
def test_tmp_path_factory(tmp_path_factory):
"""For session-scoped temporary dirs (shared across tests)."""
shared_dir = tmp_path_factory.mktemp("hogwarts_cache")
(shared_dir / "student_list.txt").write_text("Harry\nRon\nHermione")
assert (shared_dir / "student_list.txt").exists()
The Sorting Hat sees into your mind. The request fixture gives you introspection into the current test โ its name, markers, params, and more.
@pytest.fixture
def adaptive_fixture(request):
"""Adapts behavior based on test markers โ like the Sorting Hat."""
if request.node.get_closest_marker("dark_arts"):
return {"defense_level": "maximum", "location": "Room of Requirement"}
else:
return {"defense_level": "normal", "location": "Classroom"}
@pytest.mark.dark_arts
def test_practice_patronus(adaptive_fixture):
assert adaptive_fixture["location"] == "Room of Requirement"
def test_regular_lesson(adaptive_fixture):
assert adaptive_fixture["location"] == "Classroom"