Test Requirements¶
The @requires_tests decorator declares what tests a function needs. The companion pytest plugin verifies those tests exist at collection time — before any test runs.
Basic Usage¶
from specwright import requires_tests
@requires_tests(
happy_path=True,
edge_cases=["empty_input", "max_boundaries"],
error_cases=["invalid_email", "negative_age"],
)
def create_user(email: str, age: int) -> dict:
"""Create a new user account."""
...
This tells Specwright: "This function requires five test functions to exist."
Expected Test Names¶
The naming convention is:
Check what's expected:
reqs = create_user.__test_requirements__
reqs.expected_test_names
# ['test_create_user_happy_path',
# 'test_create_user_empty_input',
# 'test_create_user_max_boundaries',
# 'test_create_user_invalid_email',
# 'test_create_user_negative_age']
Writing the Tests¶
Create test functions that match the expected names:
# tests/test_create_user.py
from myapp import create_user
def test_create_user_happy_path():
result = create_user("alice@example.com", 30)
assert result["email"] == "alice@example.com"
assert result["age"] == 30
def test_create_user_empty_input():
# Test with empty string email
...
def test_create_user_max_boundaries():
# Test with extreme age values
...
def test_create_user_invalid_email():
# Test with malformed email
...
def test_create_user_negative_age():
# Test with negative age
...
Decorator Options¶
@requires_tests(
happy_path=True, # Require a _happy_path test (default: True)
edge_cases=["empty", "boundary"], # Edge case scenario names
error_cases=["invalid_input", "overflow"], # Error case scenario names
)
Happy Path Only¶
@requires_tests()
def simple_function(x: int) -> int:
...
# Expects: test_simple_function_happy_path
No Happy Path¶
@requires_tests(happy_path=False, edge_cases=["empty", "full"])
def edge_only(items: list) -> list:
...
# Expects: test_edge_only_empty, test_edge_only_full
Pytest Plugin¶
Specwright includes a pytest plugin that automatically checks test requirements at collection time. It's registered via the pytest11 entry point and activates when Specwright is installed.
Configuration¶
Set the enforcement level in pyproject.toml:
| Mode | Behavior |
|---|---|
strict |
Fail the session if any required tests are missing |
warn |
Emit warnings but let the session continue |
off |
Skip the check entirely |
Strict Mode (Default)¶
If any required test is missing, the session fails immediately:
ERRORS
MissingTestsError: Missing required tests:
create_user:
- test_create_user_empty_input
- test_create_user_negative_age
Warn Mode¶
Missing tests produce warnings but don't block the session:
Off Mode¶
No checking at all. Useful when running a subset of tests during development:
Combining with @spec¶
@requires_tests composes naturally with @spec:
from specwright import requires_tests, spec
@requires_tests(
happy_path=True,
edge_cases=["empty_list", "single_item"],
error_cases=["non_numeric"],
)
@spec
def average(values: list[float]) -> float:
"""Calculate the arithmetic mean of a list of numbers."""
return sum(values) / len(values)
Both decorators store their metadata independently:
TestRequirements Dataclass¶
The metadata stored on decorated functions:
@dataclass(frozen=True)
class TestRequirements:
function_name: str # Simple function name
qualname: str # Qualified name (includes class if nested)
module: str | None # Module where the function is defined
happy_path: bool # Whether a happy-path test is required
edge_cases: tuple[str, ...] # Edge case scenario names
error_cases: tuple[str, ...] # Error case scenario names
The expected_test_names property computes the full list of required test function names.
Global Registry¶
All @requires_tests-decorated functions are tracked in a global registry. The pytest plugin reads this registry to know what to check:
from specwright.testing import get_registry
registry = get_registry() # Returns a copy of all registered TestRequirements
Why this matters for LLM-assisted development
This enforces a test-driven LLM workflow: the human declares what must be tested (happy path, edge cases, error cases), the LLM writes both the implementation and the tests, and the framework verifies nothing is forgotten. You can't ship code with missing tests — the pytest plugin catches it before any test runs.
Common Pitfalls¶
Test names must match exactly
The plugin checks for exact function name matches. A test named test_create_user_happy won't satisfy a requirement for test_create_user_happy_path.
Test enforcement affects all tests in the session
If any @requires_tests-decorated function in your codebase has missing tests, strict mode will fail the entire session. Use "off" mode during early development.
Registry is global and cumulative
Every import of a module containing @requires_tests adds to the global registry. In tests, you may need to clear the registry between test cases if you're dynamically creating decorated functions.