PHPUnit: Fundamentos e Configuração
PHPUnit é o framework de testes mais consolidado no ecossistema PHP. Ele oferece uma estrutura robusta para escrever testes unitários, de integração e funcionais. Em Laravel, PHPUnit vem pré-configurado no arquivo phpunit.xml, permitindo que você comece a escrever testes imediatamente. A configuração padrão já cobre a maioria dos cenários, incluindo ambiente de teste separado e carregamento automático de fixtures.
Para começar, crie um teste simples na pasta tests/Unit. Todo teste em PHPUnit herda de TestCase e cada método de teste deve começar com test. Aqui está um exemplo prático:
<?php
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;
class CalculadoraTest extends TestCase
{
public function testSomaDoisNumeros()
{
$resultado = 2 + 2;
$this->assertEquals(4, $resultado);
}
public function testDivisaoPorZeroLancaExcecao()
{
$this->expectException(\DivisionByZeroError::class);
$resultado = 1 / 0;
}
}
Execute com php artisan test ou vendor/bin/phpunit. O PHPUnit oferece várias asserções como assertEquals(), assertTrue(), assertStringContainsString() e muitas outras para validar comportamentos esperados.
Testando Controllers e Models com PHPUnit
Em testes de feature no Laravel, você precisará testar controllers, models e a integração entre eles. A classe Tests\TestCase fornece helpers como $this->get(), $this->post() e $this->json() para fazer requisições HTTP simuladas. Aqui está um exemplo realista:
<?php
namespace Tests\Feature;
use App\Models\User;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
class UserControllerTest extends TestCase
{
use RefreshDatabase;
public function testListaUsuarios()
{
User::factory()->count(3)->create();
$response = $this->get('/api/users');
$response->assertStatus(200);
$response->assertJsonCount(3);
}
public function testCriaUsuarioComDadosValidos()
{
$dados = [
'name' => 'João Silva',
'email' => 'joao@example.com',
'password' => 'senha123456',
];
$response = $this->post('/api/users', $dados);
$response->assertStatus(201);
$this->assertDatabaseHas('users', ['email' => 'joao@example.com']);
}
public function testRejeitaDadosInvalidos()
{
$response = $this->post('/api/users', ['name' => 'João']);
$response->assertStatus(422);
$response->assertJsonValidationErrors(['email', 'password']);
}
}
Utilize RefreshDatabase para resetar o banco em cada teste. Use factories para gerar dados de teste consistentes. As asserções assertStatus(), assertJson() e assertDatabaseHas() verificam respostas e estado do banco simultaneamente.
Pest: Sintaxe Moderna e Expressiva
Pest é um framework de testes moderno construído sobre PHPUnit que oferece uma sintaxe mais limpa e intuitiva. Apesar de ser mais recente, é totalmente compatível com PHPUnit e oferece experiência superior em desenvolvimento. A instalação é simples: composer require pestphp/pest --dev.
A principal diferença está na sintaxe. Em vez de classes e métodos, Pest usa funções globais test() e it(). Isso torna os testes mais legíveis e próximos da linguagem natural:
<?php
use App\Models\Post;
use Tests\TestCase;
uses(TestCase::class)->in('Feature');
test('usuário autenticado pode criar post', function () {
$user = User::factory()->create();
$response = $this->actingAs($user)
->post('/api/posts', [
'title' => 'Meu Post',
'content' => 'Conteúdo do post',
]);
$response->assertStatus(201);
expect(Post::count())->toBe(1);
});
it('rejeita posts com título vazio', function () {
$user = User::factory()->create();
$response = $this->actingAs($user)
->post('/api/posts', ['content' => 'Conteúdo']);
$response->assertStatus(422);
});
Pest também introduz expectativas fluentes com expect(), que são mais expressivas que asserções tradicionais. Você pode encadear múltiplas verificações: expect($resultado)->toBeInt()->toBeGreaterThan(0).
Fixtures e Setup com Pest
Pest permite definir setup compartilhado de forma elegante:
<?php
beforeEach(function () {
$this->user = User::factory()->create();
$this->admin = User::factory()->admin()->create();
});
test('admin pode deletar qualquer post', function () {
$post = Post::factory()->create();
$response = $this->actingAs($this->admin)
->delete("/api/posts/{$post->id}");
$response->assertStatus(204);
expect(Post::count())->toBe(0);
});
Esse approach com beforeEach() é mais intuitivo que setUp() no PHPUnit tradicional. Pest também suporta afterEach(), beforeAll() e afterAll() para controle fino do ciclo de vida dos testes.
Melhores Práticas e Diferenças
A escolha entre PHPUnit e Pest não é binária. PHPUnit permanece o padrão da indústria, com documentação extensa e ampla integração em IDEs. Pest é ideal para novos projetos ou times que valorizam legibilidade extrema. Ambas coexistem bem no mesmo projeto.
Independente da escolha, siga estas práticas: 1) Teste comportamento, não implementação — focalize no "o que" não no "como"; 2) Use factories e seeders para dados consistentes; 3) Mantenha testes independentes, sem compartilhamento de estado; 4) Nomeie testes descritivamente; 5) Aim para 80%+ de cobertura em código crítico, não em tudo.
// PHPUnit
$this->assertEquals($esperado, $resultado);
// vs Pest
expect($resultado)->toBe($esperado);
Ambas as abordagens são válidas. A escolha depende da preferência da equipe e do contexto do projeto. Em projetos legados, PHPUnit faz mais sentido. Em startups ágeis, Pest acelera a entrega.
Conclusão
PHPUnit e Pest são ambos ferramentas poderosas para garantir qualidade no Laravel. PHPUnit oferece solidez e compatibilidade, ideal para projetos estabelecidos. Pest traz modernidade e sintaxe intuitiva, perfeito para times que priorizam legibilidade. O fundamental é escrever testes, não qual framework escolher. Comece pequeno, teste comportamentos críticos, e expanda gradualmente sua cobertura de testes conforme o projeto cresce.