O TestClient exercita rotas FastAPI pelo contrato HTTP. Dependências caras ou externas podem ser substituídas por app.dependency_overrides, desde que o estado seja restaurado.

from fastapi.testclient import TestClient

from app.main import app, usuario_atual


def usuario_teste() -> dict[str, object]:
    return {"id": 7, "admin": False}


def test_perfil() -> None:
    app.dependency_overrides[usuario_atual] = usuario_teste
    try:
        with TestClient(app) as client:
            response = client.get("/perfil")
        assert response.status_code == 200
        assert response.json()["id"] == 7
    finally:
        app.dependency_overrides.clear()

O context manager executa eventos de ciclo de vida. Em uma suíte maior, transforme override e limpeza em fixture. Teste status, schema e efeitos relevantes, não apenas que uma função interna foi chamada.

Use banco isolado por teste, transações descartáveis ou repositórios fake conforme o nível. Não chame autenticação paga em cada teste. Consulte FastAPI, fixtures pytest e pytest-asyncio.

A documentação oficial de testes FastAPI, consultada em 22 de julho de 2026, explica TestClient e overrides. Mantenha testes unitários rápidos e uma camada menor de integração com infraestrutura real.

O que um teste de rota deve provar

Um teste útil verifica o contrato público, não a reprodução da implementação do endpoint. Envie o mesmo método, caminho, headers, query e corpo JSON que um cliente enviaria. Depois inspecione status, headers da resposta e o corpo decodificado. Em um endpoint de criação, também confira o efeito observável de persistência. Em uma regra de autorização, teste requisição anônima, autenticada sem permissão e autorizada.

Evite afirmar todos os campos quando só dois pertencem ao comportamento sob teste. Uma comparação literal completa fica barulhenta quando o schema ganha um campo opcional irrelevante. Por outro lado, checar só status_code == 200 pode mascarar um payload quebrado. Escolha asserções que descrevem o contrato: identificadores com o valor esperado, segredos ausentes, erros de validação apontando para a entrada correta e metadados de paginação coerentes.

O FastAPI valida a entrada antes de chamar o endpoint. Inclua JSON malformado, valores obrigatórios ausentes, limites e tipos errados. Não fixe a resposta completa de validação a menos que o formato exato faça parte do contrato da API. Em geral, é mais seguro encontrar o item relevante em response.json()["detail"] e afirmar localização e categoria do erro.

Monte fixtures reutilizáveis no pytest

Crie a aplicação e o client em fixtures quando o setup for compartilhado. A fixture deve ser dona de tudo o que cria e liberar após o yield. Essa regra importa porque dependency_overrides é um dicionário mutável no objeto da aplicação.

import pytest
from fastapi.testclient import TestClient

from app.main import create_app, obter_repositorio


@pytest.fixture
def repositorio_falso():
    return RepositorioUsuarioMemoria()


@pytest.fixture
def client(repositorio_falso):
    app = create_app()
    app.dependency_overrides[obter_repositorio] = lambda: repositorio_falso
    with TestClient(app) as test_client:
        yield test_client
    app.dependency_overrides.clear()

Uma factory de aplicação dá a cada teste um app novo e reduz acoplamento acidental. Se o projeto exporta um app global, salve o override anterior e restaure-o em vez de limpar overrides instalados por outra fixture. Mantenha o escopo em function até medições justificarem estado mais amplo. Fakes mutáveis com escopo de sessão costumam fazer o resultado depender da ordem de execução.

Use a forma with TestClient(app) quando a aplicação tiver lifespan. Entrar no contexto inicia recursos como pools de conexão; sair executa o encerramento. Instanciar o client sem o context manager só é aceitável quando o comportamento não depende desses eventos.

Substitua dependências na fronteira certa

Substitua o callable passado a Depends, não um helper diferente chamado por acaso dentro dele. A chave do dicionário é o objeto da função original. A substituição pode ser síncrona ou assíncrona, conforme o caso, e deve devolver um valor compatível.

Autenticação é uma boa fronteira. Devolva um usuário de domínio pequeno com permissões explícitas e teste a decodificação do token separadamente. Um repositório ou serviço também é fronteira útil porque evita rede e banco, mas mantém parsing, injeção, lógica do endpoint e serialização.

Não substitua tudo. Se validação, serialização ou mapeamento de exceções for o assunto, mantenha esses componentes reais. Um teste em que todos os colaboradores são mocks pode passar enquanto a rota montada é inutilizável. Reserve testes diretos de função para regras de domínio complexas e testes de rota para integração HTTP.

Teste exceções, headers e segurança

Para falhas conhecidas, afirme o status previsto e uma mensagem pública segura. Um registro ausente pode gerar 404; um recurso duplicado, 409. Detalhes internos, SQL, tokens e stack traces não devem aparecer. Se a API instala handlers de exceção customizados, exercite-os por uma rota.

Testes de autenticação devem incluir header ausente, esquema inválido, credencial expirada e escopo insuficiente quando aplicável. Não coloque credenciais reais no código nem na saída capturada. Construa tokens sintéticos com chaves só de teste, ou substitua a dependência de identidade já verificada.

Para cookies, redirects, downloads e respostas em streaming, inspecione o comportamento HTTP correspondente. O TestClient segue redirects por padrão, o que pode esconder o 307 ou 302 original; desative o follow quando o redirect em si for o contrato.

Testes async com HTTPX

O TestClient é síncrono mesmo quando a rota usa async def. É conveniente para a maioria dos testes de endpoint. Use um teste assíncrono quando o próprio teste precisar aguardar um repositório, fila ou sessão async. A documentação do FastAPI descreve HTTPX com ASGITransport nesse caso.

import pytest
from httpx import ASGITransport, AsyncClient


@pytest.mark.anyio
async def test_health(app) -> None:
    transport = ASGITransport(app=app)
    async with AsyncClient(
        transport=transport,
        base_url="http://test",
    ) as client:
        response = await client.get("/health")
    assert response.status_code == 200
    assert response.json() == {"status": "ok"}

O transport envia requisições direto à aplicação ASGI e não abre uma porta real. O gerenciamento de lifespan pode exigir fixture explícita conforme a configuração do HTTPX. Mantenha o plugin do runner e a política de event loop consistentes.

Isolamento de banco e níveis de teste

Escolha o realismo do banco de propósito. Um repositório em memória é rápido, mas não revela sintaxe SQL, constraints, comportamento de transação ou diferenças de driver. SQLite não substitui fielmente todo recurso do PostgreSQL. Mantenha uma suíte menor de integração contra o engine usado em produção, em geral com banco descartável ou container.

Fixtures baseadas em rollback são rápidas, mas código que abre conexões independentes ou faz commit próprio pode escapar do rollback externo. Confirme as fronteiras reais de conexão e transação. Schemas únicos ou bancos recriados dão isolamento mais forte com custo maior de setup.

Organize a suíte em muitos testes unitários focados, testes de contrato de rota com dependências controladas e menos testes de integração de infraestrutura. Adicione ponta a ponta só para jornadas críticas. Essa distribuição dá feedback rápido sem fingir que fakes provam compatibilidade com o banco.

Mantenha a suíte determinística

Congele o tempo por uma dependência de relógio em vez de patchar muitas chamadas de biblioteca. Gere identificadores estáveis ou afirme o formato, não um valor aleatório exato. Desative acesso de rede de saída por padrão para que um override omitido falhe imediatamente. Para tarefas em background, verifique a intenção registrada sem contatar o provedor real.

Execução em paralelo expõe estado compartilhado oculto. Evite repositórios mutáveis em nível de módulo, linhas reutilizadas, nomes temporários fixos e overrides globais. Um teste que falha deve ser reproduzível sozinho e em ordem aleatória.

Antes do merge, rode a suíte de rotas com warnings visíveis. Trate depreciações de FastAPI, Starlette, Pydantic e HTTPX como sinais de manutenção. Bons testes de API documentam comportamento, pegam upgrades incompatíveis e permanecem legíveis o bastante para um revisor entender o contrato prometido sem abrir a implementação do endpoint.