py-testing-async
Async testing patterns with pytest-asyncio. Use when writing tests, mocking async code, testing database operations, or debugging test failures.
Best use case
py-testing-async is best used when you need a repeatable AI agent workflow instead of a one-off prompt. It is especially useful for teams working in multi. Async testing patterns with pytest-asyncio. Use when writing tests, mocking async code, testing database operations, or debugging test failures.
Async testing patterns with pytest-asyncio. Use when writing tests, mocking async code, testing database operations, or debugging test failures.
Users should expect a more consistent workflow output, faster repeated execution, and less time spent rewriting prompts from scratch.
Practical example
Example input
Use the "py-testing-async" skill to help with this workflow task. Context: Async testing patterns with pytest-asyncio. Use when writing tests, mocking async code, testing database operations, or debugging test failures.
Example output
A structured workflow result with clearer steps, more consistent formatting, and an output that is easier to reuse in the next run.
When to use this skill
- Use this skill when you want a reusable workflow rather than writing the same prompt again and again.
When not to use this skill
- Do not use this when you only need a one-off answer and do not need a reusable workflow.
- Do not use it if you cannot install or maintain the related files, repository context, or supporting tools.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/py-testing-async/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How py-testing-async Compares
| Feature / Agent | py-testing-async | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Async testing patterns with pytest-asyncio. Use when writing tests, mocking async code, testing database operations, or debugging test failures.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
Related Guides
SKILL.md Source
# Python Async Testing
## Problem Statement
Async testing requires specific patterns. pytest-asyncio has modes that affect behavior. Database tests need isolation. Mocking async functions differs from sync. Get these wrong and tests are flaky or don't catch bugs.
---
## Pattern: pytest-asyncio Configuration
**Problem:** Pytest needs configuration for async tests.
```toml
# pyproject.toml
[tool.pytest.ini_options]
asyncio_mode = "strict" # Requires explicit @pytest.mark.asyncio
# OR
asyncio_mode = "auto" # All async tests run automatically
```
```python
import pytest
# With asyncio_mode = "strict" (this codebase)
@pytest.mark.asyncio
async def test_something():
result = await some_async_function()
assert result == expected
# Without the marker = test won't run as async!
```
---
## Pattern: Async Fixtures
**Problem:** Fixtures that provide async resources need specific handling.
```python
import pytest
from sqlalchemy.ext.asyncio import AsyncSession
# ✅ CORRECT: Async fixture for session
@pytest.fixture
async def session() -> AsyncGenerator[AsyncSession, None]:
async with async_session() as session:
yield session
await session.rollback() # Clean up after test
# ✅ CORRECT: Async fixture for test data
@pytest.fixture
async def test_user(session: AsyncSession) -> User:
user = User(email="test@example.com", hashed_password="...")
session.add(user)
await session.commit()
await session.refresh(user)
return user
# ✅ CORRECT: Using async fixtures
@pytest.mark.asyncio
async def test_get_user(session: AsyncSession, test_user: User):
result = await session.execute(
select(User).where(User.id == test_user.id)
)
user = result.scalar_one()
assert user.email == "test@example.com"
```
---
## Pattern: Database Test Isolation
**Problem:** Tests polluting each other's database state.
```python
# ✅ CORRECT: Transaction rollback per test
@pytest.fixture
async def session() -> AsyncGenerator[AsyncSession, None]:
async with async_session() as session:
# Start a transaction
async with session.begin():
yield session
# Rollback happens automatically when we exit
# ✅ CORRECT: Nested transactions for complex tests
@pytest.fixture
async def session() -> AsyncGenerator[AsyncSession, None]:
async with async_session() as session:
await session.begin()
yield session
await session.rollback()
# Alternative: Use separate test database
# conftest.py
@pytest.fixture(scope="session")
def event_loop():
loop = asyncio.get_event_loop_policy().new_event_loop()
yield loop
loop.close()
@pytest.fixture(scope="session")
async def test_engine():
# Use SQLite for tests, PostgreSQL for prod
engine = create_async_engine("sqlite+aiosqlite:///:memory:")
async with engine.begin() as conn:
await conn.run_sync(SQLModel.metadata.create_all)
yield engine
await engine.dispose()
```
---
## Pattern: Mocking Async Functions
**Problem:** Regular `Mock` doesn't work with async functions.
```python
from unittest.mock import AsyncMock, patch
# ✅ CORRECT: AsyncMock for async functions
@pytest.mark.asyncio
async def test_with_mocked_service():
mock_service = AsyncMock()
mock_service.get_user.return_value = User(id=uuid4(), email="test@example.com")
result = await mock_service.get_user(user_id)
assert result.email == "test@example.com"
mock_service.get_user.assert_called_once_with(user_id)
# ✅ CORRECT: Patching async functions
@pytest.mark.asyncio
@patch("app.services.user_service.send_email", new_callable=AsyncMock)
async def test_user_creation_sends_email(mock_send_email: AsyncMock, session: AsyncSession):
mock_send_email.return_value = True
user = await create_user(email="new@example.com", session=session)
mock_send_email.assert_called_once_with(user.email, "Welcome!")
# ✅ CORRECT: AsyncMock with side_effect
mock_service = AsyncMock()
mock_service.get_user.side_effect = [
User(id=uuid4(), email="first@example.com"),
User(id=uuid4(), email="second@example.com"),
]
# First call returns first user, second call returns second user
```
---
## Pattern: HTTP Client Testing
**Problem:** Testing FastAPI endpoints with async client.
```python
import pytest
from httpx import AsyncClient, ASGITransport
from app.main import app
@pytest.fixture
async def client() -> AsyncGenerator[AsyncClient, None]:
async with AsyncClient(
transport=ASGITransport(app=app),
base_url="http://test",
) as client:
yield client
@pytest.mark.asyncio
async def test_get_users(client: AsyncClient):
response = await client.get("/api/users")
assert response.status_code == 200
data = response.json()
assert isinstance(data, list)
@pytest.mark.asyncio
async def test_create_assessment(client: AsyncClient, auth_headers: dict):
response = await client.post(
"/api/assessments",
json={"title": "Test Assessment", "skill_areas": ["fundamentals"]},
headers=auth_headers,
)
assert response.status_code == 201
data = response.json()
assert data["title"] == "Test Assessment"
# ✅ CORRECT: Auth fixture
@pytest.fixture
async def auth_headers(test_user: User) -> dict:
token = create_access_token(user_id=test_user.id)
return {"Authorization": f"Bearer {token}"}
```
---
## Pattern: Testing Service Functions
```python
@pytest.mark.asyncio
async def test_calculate_rating(session: AsyncSession, test_user: User):
# Arrange: Create test data
assessment = Assessment(user_id=test_user.id, title="Test")
session.add(assessment)
await session.commit()
answers = [
UserAnswer(user_id=test_user.id, question_id=q_id, value=4)
for q_id in question_ids
]
session.add_all(answers)
await session.commit()
# Act: Call the service
result = await calculate_rating(assessment.id, session)
# Assert: Check the result
assert result.rating >= 1.0
assert result.rating <= 5.5
assert result.confidence > 0
@pytest.mark.asyncio
async def test_calculate_rating_no_answers(session: AsyncSession, test_user: User):
assessment = Assessment(user_id=test_user.id, title="Empty")
session.add(assessment)
await session.commit()
# Should raise or return specific result
with pytest.raises(ValueError, match="No answers found"):
await calculate_rating(assessment.id, session)
```
---
## Pattern: Testing Multi-Step Flows
Same principle as frontend - test entire flows, not just units:
```python
@pytest.mark.asyncio
async def test_complete_assessment_flow(session: AsyncSession, test_user: User):
"""Test full assessment flow: create -> answer -> submit -> results."""
# Step 1: Create assessment
assessment = await create_assessment(
user_id=test_user.id,
data=AssessmentCreate(title="Full Flow Test", skill_areas=["fundamentals"]),
session=session,
)
assert assessment.id is not None
# Step 2: Answer questions
questions = await get_assessment_questions(assessment.id, session)
for question in questions:
await submit_answer(
user_id=test_user.id,
question_id=question.id,
value=4,
session=session,
)
# Step 3: Submit assessment
result = await submit_assessment(assessment.id, session)
assert result.status == "completed"
# Step 4: Verify results
rating = await get_assessment_rating(assessment.id, session)
assert rating is not None
assert rating.skill_area == "fundamentals"
```
---
## Pattern: Fixture Scopes
**Problem:** Understanding when fixtures are recreated.
```python
# function (default) - recreated for each test
@pytest.fixture
async def session():
... # New session per test
# class - shared within test class
@pytest.fixture(scope="class")
async def shared_data():
... # Created once per test class
# module - shared within test file
@pytest.fixture(scope="module")
async def module_setup():
... # Created once per file
# session - shared across entire test run
@pytest.fixture(scope="session")
async def database():
... # Created once, used by all tests
```
**Best practices:**
- Database sessions: `function` scope (isolation)
- Test data: `function` scope (clean state)
- Database engine: `session` scope (expensive to create)
---
## Pattern: Testing Error Cases
```python
@pytest.mark.asyncio
async def test_get_nonexistent_user(session: AsyncSession):
fake_id = uuid4()
with pytest.raises(HTTPException) as exc_info:
await get_user_or_404(fake_id, session)
assert exc_info.value.status_code == 404
assert str(fake_id) in exc_info.value.detail
@pytest.mark.asyncio
async def test_duplicate_email_rejected(session: AsyncSession, test_user: User):
with pytest.raises(IntegrityError):
duplicate = User(email=test_user.email, hashed_password="...")
session.add(duplicate)
await session.commit()
```
---
## Pattern: Parameterized Tests
```python
@pytest.mark.asyncio
@pytest.mark.parametrize("skill_area,expected_min,expected_max", [
("fundamentals", 1.0, 5.5),
("advanced", 1.0, 5.5),
("strategy", 1.0, 5.5),
])
async def test_rating_ranges(
skill_area: str,
expected_min: float,
expected_max: float,
session: AsyncSession,
):
rating = await calculate_rating_for_area(skill_area, session)
assert expected_min <= rating <= expected_max
@pytest.mark.asyncio
@pytest.mark.parametrize("invalid_input", [
{"title": ""}, # Empty title
{"title": "x" * 201}, # Too long
{"skill_areas": []}, # Empty areas
])
async def test_assessment_validation(invalid_input: dict, client: AsyncClient):
response = await client.post("/api/assessments", json=invalid_input)
assert response.status_code == 422
```
---
## Common Issues
| Issue | Likely Cause | Solution |
|-------|--------------|----------|
| "coroutine was never awaited" | Missing `await` in test | Add `await` |
| Test not running async | Missing `@pytest.mark.asyncio` | Add marker or use `asyncio_mode = "auto"` |
| Tests polluting each other | Missing rollback | Use transaction fixture with rollback |
| "Event loop is closed" | Fixture scope mismatch | Check `scope` on async fixtures |
| Mock not working | Using `Mock` instead of `AsyncMock` | Use `AsyncMock` for async |
---
## Test Commands
```bash
# Run all tests
uv run pytest
# Verbose output
uv run pytest -v
# Specific file
uv run pytest tests/test_assessments.py
# Specific test
uv run pytest tests/test_assessments.py::test_create_assessment
# With coverage
uv run pytest --cov=app --cov-report=html
# Stop on first failure
uv run pytest -x
# Show print output
uv run pytest -s
```Related Skills
testing-strategies
Design comprehensive testing strategies for software quality assurance. Use when planning test coverage, implementing test pyramids, or setting up testing infrastructure. Handles unit testing, integration testing, E2E testing, TDD, and testing best practices.
backend-testing
Write comprehensive backend tests including unit tests, integration tests, and API tests. Use when testing REST APIs, database operations, authentication flows, or business logic. Handles Jest, Pytest, Mocha, testing strategies, mocking, and test coverage.
wordpress-penetration-testing
This skill should be used when the user asks to "pentest WordPress sites", "scan WordPress for vulnerabilities", "enumerate WordPress users, themes, or plugins", "exploit WordPress vulnerabilities", or "use WPScan". It provides comprehensive WordPress security assessment methodologies.
web3-testing
Test smart contracts comprehensively using Hardhat and Foundry with unit tests, integration tests, and mainnet forking. Use when testing Solidity contracts, setting up blockchain test suites, or validating DeFi protocols.
web-security-testing
Web application security testing workflow for OWASP Top 10 vulnerabilities including injection, XSS, authentication flaws, and access control issues.
unit-testing-test-generate
Generate comprehensive, maintainable unit tests across languages with strong coverage and edge case focus.
testing-qa
Comprehensive testing and QA workflow covering unit testing, integration testing, E2E testing, browser automation, and quality assurance.
temporal-python-testing
Test Temporal workflows with pytest, time-skipping, and mocking strategies. Covers unit testing, integration testing, replay testing, and local development setup. Use when implementing Temporal workflow tests or debugging test failures.
ssh-penetration-testing
This skill should be used when the user asks to "pentest SSH services", "enumerate SSH configurations", "brute force SSH credentials", "exploit SSH vulnerabilities", "perform SSH tunneling", or "audit SSH security". It provides comprehensive SSH penetration testing methodologies and techniques.
sqlmap-database-pentesting
This skill should be used when the user asks to "automate SQL injection testing," "enumerate database structure," "extract database credentials using sqlmap," "dump tables and columns...
sqlmap-database-penetration-testing
This skill should be used when the user asks to "automate SQL injection testing," "enumerate database structure," "extract database credentials using sqlmap," "dump tables and columns from a vulnerable database," or "perform automated database penetration testing." It provides comprehensive guidance for using SQLMap to detect and exploit SQL injection vulnerabilities.
sql-injection-testing
This skill should be used when the user asks to "test for SQL injection vulnerabilities", "perform SQLi attacks", "bypass authentication using SQL injection", "extract database information through injection", "detect SQL injection flaws", or "exploit database query vulnerabilities". It provides comprehensive techniques for identifying, exploiting, and understanding SQL injection attack vectors across different database systems.