Testing with pytest
pytest idioms for unit tests - fixtures, parametrization, and patching. Results appear in the same fence: same-line # when short, multiline # blocks below the sample when not.
Search across all documentation pages
pytest idioms for unit tests - fixtures, parametrization, and patching. Results appear in the same fence: same-line # when short, multiline # blocks below the sample when not.
Plain assert gets introspection - no need for assertEqual helpers.
def add(a: int, b: int) -> int:
return a + b
assert add(2, 3) == 5 # passes
add(2, 3) # 5Share expensive setup with fixture scopes (function, module, session).
# @pytest.fixture(scope="module")
# def db():
# conn = connect(); yield conn; conn.close()
scopes = ("function", "module", "session")
scopes[0] # 'function'Run the same test across a table of inputs.
cases = [(1, 1, 2), (2, 3, 5)]
[(x + y == out) for x, y, out in cases] # [True, True]
# @pytest.mark.parametrize("x,y,out", cases)Patch env vars and attributes safely per test.
import os
os.environ["PORT"] = "9999"
int(os.environ["PORT"]) # 9999
# monkeypatch.setenv("PORT", "9999") inside a testExpect exceptions and inspect them.
import pytest
with pytest.raises(ZeroDivisionError):
1 / 0
# passes when ZeroDivisionError is raised
True # TrueWrite test files into a per-test temporary directory.
from pathlib import Path
import tempfile
tmp_path = Path(tempfile.mkdtemp()) # like pytest tmp_path
p = tmp_path / "a.txt"
p.write_text("hi", encoding="utf-8")
p.read_text(encoding="utf-8") # 'hi'Compare floats with tolerance via pytest.approx.
import pytest
0.1 + 0.2 == pytest.approx(0.3) # TrueSkip or xfail tests when conditions are not met.
import sys
sys.platform == "win32" # True or False depending on OS
# @pytest.mark.skipif(sys.platform == "win32", reason="posix only")Assert log messages with the caplog fixture.
import logging, io
buf = io.StringIO()
log = logging.getLogger("t"); log.addHandler(logging.StreamHandler(buf))
log.setLevel(logging.INFO); log.info("started")
"started" in buf.getvalue() # TrueReplace functions on modules for isolation.
class Mod:
@staticmethod
def fetch(url: str) -> bytes: return b"net"
mod = Mod()
mod.fetch = lambda url: b"ok" # type: ignore[method-assign]
mod.fetch("x") # b'ok'Put shared fixtures in conftest.py for discovery without imports.
# tests/conftest.py
# @pytest.fixture
# def client():
# return TestClient(app)
path = "tests/conftest.py"
path.endswith("conftest.py") # TrueApply fixtures without naming them as parameters.
# @pytest.mark.usefixtures("migrate_db")
# def test_query(): ...
marker = "usefixtures"
"fixture" in marker # TrueReturn a factory function from a fixture for flexible setup.
def make_user(**kw):
return {"name": "a", **kw}
make_user(name="Ada") # {'name': 'Ada'}
make_user(id=1) # {'name': 'a', 'id': 1}Async tests need pytest-asyncio or similar - mark async tests per project config.
import asyncio
async def fetch() -> int:
return 1
asyncio.run(fetch()) # 1
# @pytest.mark.asyncio
# async def test_fetch(): assert await fetch() == 1Add context to assertions with messages or richer comparison objects.
user = {"id": "u1", "active": True}
assert user["active"], f"user {user['id']} should be active" # passes
user["id"] # 'u1'Stack versions: Python 3.14.0 (stable 3.14, maintenance 3.13) · uv 0.6+ · ruff 0.9+ · pytest
Reviewed by Chris St. John·Last updated Jul 31, 2026