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.