Loaded automatically when its description matches the active task. Read only the section you need, then follow the link to the relevant reference file.
Use this skill when
Setting up pytest in a new project (pyproject.toml [tool.pytest.ini_options], testpaths, addopts, conftest.py)
Writing tests with assert, pytest.raises, pytest.warns, pytest.approx, pytest.mark.*
Task is JS/TS unit testing (Vitest/Jest semantics, not pytest) — use vitest
Task is browser E2E automation — use playwright
Task is Django built-in TestCase/Client without pytest involved — use django directly
Task is unittest module (stdlib) idiom without pytest — different runner, different conventions
Task is load testing, BDD (pytest-bdd only marginally — confirm scope), or contract testing
Purpose
pytest is the de-facto Python test framework in 2026. It replaces unittest boilerplate with plain assert, rich introspection on failure, and a fixture system that composes deterministic test state without inheritance hierarchies. Around pytest sits the most mature plugin ecosystem in Python testing: pytest-cov for coverage, pytest-asyncio for async def tests, pytest-xdist for parallel runs, pytest-mock for the mocker fixture, Hypothesis for property-based generation.
pytest 9 dropped Python 3.9, promoted PytestRemovedIn9Warning to errors, added native TOML support (pytest.toml / [tool.pytest] in pyproject.toml with real TOML types — not the legacy INI-compat mode), shipped first-party subtests, and tightened duplicate-path collection. This skill covers v9 specifics, the full fixture + parametrize + mark surface, mocking (with the common unittest.mock vs pytest-mock mocker confusion resolved), async testing, coverage, parallelism, and the most common failure modes encountered when scaling a test suite.
Capabilities
Test discovery & invocation
pytest discovers files named test_*.py or *_test.py, classes named Test* (no __init__), and functions/methods named test_*. Layout: src/<pkg>/ for code, tests/ for tests at repo root; tests/conftest.py shares fixtures across the tree. Run with pytest, narrow with pytest tests/test_foo.py::TestClass::test_method, filter by keyword -k 'login and not legacy', by marker -m 'slow', fail fast -x, show locals on failure -l, control traceback style --tb=short|long|line|native|no.
@pytest.fixture produces test inputs and cleanup. Scopes: function (default), class, module, package, session. yield-style fixtures run teardown after the yield. autouse=True applies a fixture to every test in its scope — use sparingly. Fixtures resolve by name in the test signature; conftest.py files share fixtures hierarchically (closest conftest.py wins). Factory pattern: a fixture returns a callable that builds objects on demand. Parametrized fixtures (params=[...]) multiply downstream tests; request.param reads the current value. indirect=True on @pytest.mark.parametrize routes params through the fixture.
@pytest.mark.parametrize("a,b,expected", [(1, 2, 3), ...]) produces one test per row. Stack multiple @parametrize decorators for cartesian product. pytest.param(value, marks=pytest.mark.xfail, id="case-name") attaches per-row marks and stable IDs. ids= accepts a list or callable. indirect=("fixture_name",) makes a param flow through the fixture instead of being injected directly.
Built-ins: skip, skipif(condition, reason=...), xfail (run but expect failure; strict=True fails on unexpected pass), parametrize, usefixtures, filterwarnings. Custom marks register in pyproject.toml under [tool.pytest.ini_options].markers, run via pytest -m slow. Always set --strict-markers so typos in mark names raise instead of silently passing.
unittest.mock.patch (stdlib): use as decorator @patch("pkg.mod.func"), context manager with patch(...), or patch.object(cls, "method"). Mocks are torn down automatically. autospec=True enforces the target's signature.
pytest-mockmocker fixture: mocker.patch("pkg.mod.func", return_value=...). No decorator stacking, no nested with, automatic teardown via fixture scope. Preferred in pytest-native code.
Both wrap the same MagicMock. side_effect=callable | exception | iterable, return_value=..., mock.assert_called_with(...), mock.assert_called_once_with(...), ANY for "don't care" args. Always patch where the name is looked up, not where it's defined.
Install pytest-asyncio. Default mode is strict — mark each async test with @pytest.mark.asyncio and async fixtures with @pytest_asyncio.fixture. Set asyncio_mode = "auto" in config to auto-mark every async def test_*. Control event-loop sharing with loop_scope="session" etc. For libraries using anyio, use the anyio_backend fixture instead.
Hypothesis generates inputs from @given(st.integers(), st.text()) and shrinks failing cases to minimal counter-examples. settings(max_examples=200, deadline=500) tunes per-test budget. Example database persists past failures for regression. Works with pytest fixtures via @given(...) applied after@pytest.fixture-using parameters in the signature.
syrupy provides a snapshot fixture: assert result == snapshot. First run records, subsequent runs compare. Update with --snapshot-update. Use snapshots for stable serialized output (HTML, JSON dumps, rendered text); avoid for values where any change is meaningful — use explicit assertions instead.
Common failures: collection errors from conftest.py import-time exceptions, fixture 'X' not found (typo or wrong conftest layer), async test hangs (missing pytest-asyncio install or wrong mode), parametrize ID encoding for non-ASCII values, flaky tests from shared module state or time-dependent assertions, slow suite detection with --durations=10.
Use pytest-randomly always (order independence is a feature, not a chore). Use pyproject.toml for config. Keep --strict-markers and xfail_strict = true in addopts. Put conftest.py at the lowest common ancestor of tests that share fixtures — not at the repo root by default. Treat autouse as a code smell unless the fixture is truly always-on (e.g., DB transaction rollback).