ProTest
Async-first testing with explicit dependency injection.
ProTest is a Python testing framework designed for modern async applications. Instead of implicit fixture resolution by name, ProTest requires you to explicitly declare what each test needs.
Why ProTest?
ProTest isn't just another test runner. It's built for modern Python: typed, async, and explicit.
Native I/O Concurrency
Tests run as coroutines on a single event loop. Parallel I/O tests without spawning separate processes.
Explicit Injection (IDE-Ready)
Ctrl+Click works. Your IDE knows every type. No guessing where fixtures come from.
# pytest: "db" is resolved by name — requires searching through fixtures
def test_user(db): ...
# ProTest: Ctrl+Click → Go to definition. Full type inference.
def test_user(db: Annotated[Database, Use(database)]): ...
Smart Tagging (Tag Propagation)
Tag a fixture once, and every test using it inherits the tag automatically—even through deep dependency chains.
@fixture(tags=["database"])
def db(): ...
session.bind(db)
@fixture()
def user_repo(db: Annotated[DB, Use(db)]): ... # Inherits "database" tag
session.bind(user_repo)
@session.test()
def test_users(repo: Annotated[Repo, Use(user_repo)]): ... # Also tagged "database"
No manual tagging. No forgotten markers.
Infra vs Code Errors
Know instantly if your code failed or your infrastructure crashed.
✗ test_create_user: AssertionError # Your bug - TEST FAILED
⚠ test_with_db: [FIXTURE] ConnectionError # Infra issue - SETUP ERROR
pytest reports both as FAILED, making it harder to distinguish test failures from infrastructure issues.
Typed Parameterization
No more string-matching that breaks when you rename arguments.
# pytest: Rename "code" → runtime error
@pytest.mark.parametrize("code", [200, 201])
def test_status(code): ...
# ProTest: Rename-safe, IDE-aware, type-checked
CODES = ForEach([200, 201])
@session.test()
def test_status(code: Annotated[int, From(CODES)]): ...
Tree-Based Scoping (Smart Teardown)
Resources are cleaned up as soon as they're no longer needed. Nested suites with predictive cleanup.
Managed Factories
Create test data with automatic caching and cleanup. No manual teardown.
Quick Example
from typing import Annotated
from protest import ProTestSession, Use, fixture
session = ProTestSession()
@fixture()
def database():
return {"connected": True}
@session.test()
async def test_db_connected(db: Annotated[dict, Use(database)]):
assert db["connected"] is True
Run it:
Features
- Sessions & Suites - Organize tests hierarchically
- Fixtures - Setup and teardown with automatic cleanup
- Dependency Injection - Explicit, type-safe dependencies
- Parallel Execution - Speed up I/O-bound tests
- Tags - Filter tests by category
- Plugins - Extend with custom reporters and hooks
Getting Started
New to ProTest? Start here: