Parametrization for pytest with fixture support, async support, class-method support, shared storage, and module-level case discovery. Declarative, typed, on-demand injection for sync, async, iterable, and async-iterable providers.
Extends pytest’s parametrization layer with case storages and containers. Containers attach case sets to test functions and test methods via inject_func and inject_method. Fixtures integrate natively. Async is supported. Matching case_*.py modules are scanned and loaded. Duplicate case names collapse to single instances.
Functions that yield case values. Supported forms:
- Sync
- Async
- Iterable
- Async iterable
- Fixture-dependent
CaseStorage instances hold providers. CompositeCaseStorage merges multiple storages.
CaseContainer wraps a storage and exposes:
.case().include().inject_func().inject_method()
A single container instance feeds any number of tests.
inject_func() and inject_method() create isolated containers for direct use. .include() attaches external storages to them.
If test_x.py exists alongside case_x.py, the plugin imports the case module and registers its providers. Tests using inject_func() or inject_method() receive matching providers. Matching is based on the type annotation of the case parameter.
pip install pytest-case-provider# test_example.py
import typing
from dataclasses import dataclass
from pytest_case_provider import CaseContainer
@dataclass(frozen=True)
class MyCase:
foo: int
container = CaseContainer[MyCase]()
@container.inject_func()
def test_cases(case: MyCase) -> None:
assert isinstance(case, MyCase)
class TestGroup:
@container.inject_method()
def test_group(self, case: MyCase) -> None:
assert case.foo >= 1
@container.case()
def case_small() -> MyCase:
return MyCase(foo=1)
@container.case()
async def case_async() -> MyCase:
return MyCase(foo=999)
@container.case()
def case_range() -> typing.Iterator[MyCase]:
yield MyCase(foo=10)tests/
test_math.py
case_math.py
case_math.py:
from dataclasses import dataclass
@dataclass(frozen=True)
class MathCase:
x: int
y: int
def case_x1_y2() -> MathCase:
return MathCase(1, 2)test_math.py:
from pytest_case_provider import inject_func
from tests.case_math import MathCase
@inject_func()
def test_add(case: MathCase) -> None:
assert case.x + case.y == 3The plugin imports case_math and binds its providers to the injector.
test_example.py::test_cases[case_small]
test_example.py::test_cases[case_async]
test_example.py::test_cases[case_range0]
test_example.py::test_cases[case_range1]
test_example.py::TestGroup::test_group[case_small]
test_example.py::TestGroup::test_group[case_async]
test_example.py::TestGroup::test_group[case_range0]
test_example.py::TestGroup::test_group[case_range1]
- Async requires
pytest-asyncio. - AnyIO execution paths are disabled.
- Case deduplication follows provider-name semantics.