Why Pest PHP Makes Laravel Tests a Pleasure to Write
A friendly introduction to Pest PHP: readable tests, expectations, datasets and hooks, with real Laravel examples.
Many developers know they should write tests but find them slow and boring to write. Pest PHP changes that feeling. It runs on top of PHPUnit, but tests read almost like plain English.
Let's look at what makes Pest pleasant, using everyday Laravel examples.
- Pest tests are short functions that describe behaviour.
- Expectations like
expect($x)->toBe(5)read naturally. - Datasets let one test cover many inputs without copy-paste.
PHPUnit vs Pest at a glance
class CategoryTest extends TestCase
{
public function test_guest_cannot_create_category(): void
{
$response = $this->post('/categories', ['name' => 'Books']);
$response->assertRedirect('/login');
}
}
it('does not let guests create categories', function () {
$this->post('/categories', ['name' => 'Books'])
->assertRedirect('/login');
});
Same check, less ceremony. Because Pest uses PHPUnit underneath, all of Laravel's testing helpers still work.
Expectations that read like sentences
expect($category->name)->toBe('Books');
expect($categories)->toHaveCount(3);
expect($user->isAdmin())->toBeTrue();
expect($price)->toBeGreaterThan(0)->toBeLessThan(1000);
You can chain expectations and even write your own, for example expect($order)->toBePaid(), so tests use the same words as your business.
Setting things up with hooks
beforeEach(function () {
$this->admin = User::factory()->admin()->create();
});
it('lets admins create a category', function () {
$this->actingAs($this->admin)
->post('/categories', ['name' => 'Books'])
->assertRedirect();
expect(Category::where('name', 'Books')->exists())->toBeTrue();
});
Datasets: one test, many inputs
Validation tests often repeat the same steps with different bad values. Datasets remove that repetition.
it('rejects invalid names', function (mixed $name) {
$this->actingAs($this->admin)
->post('/categories', ['name' => $name])
->assertSessionHasErrors('name');
})->with([
'empty' => [''],
'too long' => [str_repeat('a', 256)],
'not a string' => [123],
]);
Pest runs this test three times and names each run, so a failure tells you exactly which case broke.
Keeping a growing suite tidy
- Use
->group('admin')and run one group with--group=admin. - Use
->skip()while a feature is being rebuilt, with a reason. - Use
--filterto run only the test you are working on. - Put shared helpers in
tests/Pest.php.
Summary
Pest keeps the power of PHPUnit but removes the noise. Short tests, natural expectations and datasets make it easier to write tests every day, which is what really protects your application.
Laravel Testing Series Part 2: Mastering Pest PHP
Real CRUD, validation and authorization tests in Pest, with hooks, datasets, custom expectations and higher-order tests.
View Course