Receber por email
← Voltar para o catálogo

py-testing-async

Async testing patterns with pytest-asyncio. Use when writing tests, mocking async code, testing database operations, or debugging test failures.

367estrelas
Atualizado há 4 meses

Ver no GitHub ↗

Como adicionar

/plugin marketplace add aiskillstore/marketplace

O comando exato pode variar conforme o repositório. Confira o README no GitHub.

Para o autor da skill

Cole no README do seu repo

Mostra que sua skill está catalogada na Skillteca, gera backlink e tráfego rastreável.

[![Skillteca](https://www.skillteca.com.br/api/badge/py-testing-async/svg)](https://www.skillteca.com.br/skills/py-testing-async)

Skills relacionadas

Ver todas de Desenvolvimento →

Alerta por categoria

Receba novas skills de Desenvolvimento toda segunda

1 email curto, só com as skills novas de Desenvolvimento. 4 minutos de leitura, sem spam, cancela com 1 clique.

Você confirma o email no primeiro envio. Sem spam. Cancela com 1 clique.

Ler descrição completa

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.

# pyproject.toml
[tool.pytest.ini_options]
asyncio_mode = "strict"  # Requires explicit @pytest.mark.asyncio
# OR
asyncio_mode = "auto"    # All async tests run automatically
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.

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.

# ✅ 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.

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.

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

@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:

@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.

# 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
Compartilhar X LinkedIn