Capítulo 16: Testes Automatizados com Pest PHP e Database Transactions
Especialização em Backend com PHP & Laravel • Laravel 11 & PHP 8.3+ • Eloquent ORM, Sanctum, Filas, Horizon e APIs Corporativas
🗺️ Mapa Conceitual do Tópico
flowchart TD
A["Cliente HTTP / Frontend"] --> B["API Gateway / Router"]
B --> C["Controller / Handler"]
C --> D["Service Layer (Regras de Negócio)"]
D --> E["Repository / ORM (Persistência)"]
E --> F["Banco de Dados / Cache"]
subgraph ARQ["Arquitetura do Capítulo"]
G["Conceito: Testes Automatizados com Pest PHP e Database Transactions"]
H["Segurança, Validação e Resiliência"]
I["Alta Performance e Escalabilidade"]
end
D --> ARQ
style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style B fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style C fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style D fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style E fill:#fce4ec,stroke:#e91e63,stroke-width:2px
style F fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px
🏛️ 1. Pest: Sintaxe Funcional Sobre o Motor do PHPUnit
Pest não é um framework de testes concorrente do PHPUnit — ele roda em cima do PHPUnit, trocando apenas a sintaxe orientada a classes por uma sintaxe funcional mais enxuta. Isso significa que qualquer assertion, mock ou recurso do PHPUnit continua disponível, só a forma de declarar o teste muda:
<?php
// tests/Feature/ProdutoApiTest.php
use function Pest\Laravel\getJson;
it('retorna lista de produtos com sucesso', function () {
getJson('/api/v1/produtos')
->assertStatus(200)
->assertJsonStructure(['data' => [['id', 'nome', 'preco']]]);
});
Cada bloco it(...) é, por baixo dos panos, um método de teste comum — o Pest só gera essa classe automaticamente a partir da closure.
🔐 2. Autenticação em Testes com actingAs()
Testar uma rota protegida exige simular um usuário autenticado sem passar por um fluxo real de login. actingAs() injeta um usuário autenticado diretamente na sessão/guard de teste, e combinado com Model Factories (que geram dados fake consistentes) elimina a necessidade de fixtures manuais no banco:
<?php
use App\Models\User;
use function Pest\Laravel\actingAs;
use function Pest\Laravel\postJson;
it('impede usuario nao autenticado de criar pedido', function () {
postJson('/api/v1/pedidos', ['produto_id' => 1])
->assertStatus(401);
});
it('permite usuario autenticado criar pedido', function () {
$user = User::factory()->create();
actingAs($user, 'sanctum')
->postJson('/api/v1/pedidos', ['produto_id' => 1])
->assertStatus(201);
});
🔄 3. RefreshDatabase: Isolamento Entre Testes via Transação
Sem isolamento, um teste que cria um registro “vaza” esse dado para o próximo teste que rodar depois — testes deixam de ser determinísticos e passam a depender da ordem de execução. O trait RefreshDatabase resolve isso envolvendo cada teste individual numa transação de banco que é revertida (ROLLBACK) automaticamente ao final, mesmo que o teste falhe no meio:
<?php
// tests/Pest.php (configuração global)
uses(\Illuminate\Foundation\Testing\RefreshDatabase::class)->in('Feature');
Isso é dramaticamente mais rápido que recriar o schema do banco do zero a cada teste (a alternativa migrate:fresh por teste): a transação garante que qualquer INSERT/UPDATE feito durante o teste nunca é de fato persistido, sem o custo de recriar tabelas.
⚠️ Gotcha: RefreshDatabase não funciona corretamente com testes que abrem suas próprias transações manuais (ex.: testando código que usa DB::transaction() com múltiplos níveis) — nesse caso, savepoints aninhados podem mascarar um ROLLBACK que deveria ter acontecido no código testado.