Fixtures¶
Fixtures are typed dependency providers. A test requests a fixture by using its name as a function parameter.
Return-value fixtures¶
from testenix import fixture
@fixture
def customer_id() -> int:
return 42
def test_customer(customer_id: int) -> None:
assert customer_id > 0
Cleanup with generators¶
Synchronous and asynchronous generator fixtures run cleanup after the consumer:
from collections.abc import AsyncIterator
from testenix import fixture
@fixture
async def client() -> AsyncIterator[Client]:
value = await Client.connect()
try:
yield value
finally:
await value.close()
Setup, call, and teardown are separate result phases. A teardown failure remains visible even when the test body passed.
Fixture dependencies¶
Fixtures can request other fixtures:
@fixture
def database_url() -> str:
return "sqlite:///:memory:"
@fixture
def repository(database_url: str) -> Repository:
return Repository(database_url)
def test_empty_repository(repository: Repository) -> None:
assert repository.count() == 0
The dependency graph is validated before execution. Missing fixtures and cycles become collection issues instead of hanging the run.
Built-in fixtures¶
Testenix 0.3 provides two dependency-free, test-scoped built-ins by name:
from pathlib import Path
def test_isolated_file(tmp_path: Path, monkeypatch) -> None:
target = tmp_path / "value.txt"
target.write_text("ok", encoding="utf-8")
monkeypatch.setenv("TESTENIX_EXAMPLE", "enabled")
assert target.read_text(encoding="utf-8") == "ok"
tmp_path is a fresh pathlib.Path removed during teardown. monkeypatch supports reversible
object/attribute and dotted-import setattr, setenv, and idempotent undo. Changes are restored
in LIFO order even when the test fails. Other pytest monkeypatch operations and pytest built-ins
such as capsys, caplog, and request are not native Testenix fixtures.
Autouse fixtures¶
Use autouse=True when setup and cleanup must apply to every test that can see a fixture:
@fixture(autouse=True)
def isolated_environment(monkeypatch):
monkeypatch.setenv("APP_ENV", "test")
yield
Explicitly requesting the same fixture still resolves one cached value for the test. A local fixture definition overrides a visible imported definition with the same name before Testenix chooses which fixtures run automatically.
Scopes¶
@fixture(scope="module")
def module_resource() -> Resource:
return Resource()
@fixture(scope="session")
def worker_resource() -> Resource:
return Resource()
Scope |
Lifetime in Testenix 0.3 |
|---|---|
|
One instance for one concrete test attempt. |
|
Shared by normal tests from the module inside one worker. |
|
Shared by normal tests assigned to one worker process. |
Session scope is currently worker-local, not a single process-global instance. Timed tests run in dedicated processes and therefore receive isolated module/session fixtures.
Custom fixture names¶
@fixture(name="api")
def build_client() -> Client:
return Client()
def test_health(api: Client) -> None:
assert api.health() == "ok"
The custom name changes dependency lookup without changing the Python function.