If you’re building Laravel apps, you already know that tests are the best safety net you can invest in. However, traditional testing can often feel "heavy" due to the amount of boilerplate code required.
Pest is a testing framework built on top of PHPUnit that provides a cleaner, more expressive syntax. It allows you to write tests that feel closer to "plain language" while still running PHPUnit under the hood.
In this article, we’ll cover:
- what Pest is (and why Laravel devs like it),
- how to install it in a Laravel project,
- how to write common test types,
- and a migration approach from PHPUnit.
Why Laravel Developers Love Pest
Pest is designed around the idea of "Less boilerplate, more intent". Instead of creating long class files with repeated method signatures, you write simple it() or test() blocks.
Key Benefits:
- Readability: You focus on behavior ("it does X") rather than structure.
- Minimalism: No need for class wrappers for small tests [3].
- Modern Helpers: It includes powerful tools like datasets, fluent expectations, and clean grouping.
- Easy Adoption: Since it runs on PHPUnit, you can migrate your existing suite gradually.
Instead of creating long test classes, you write simple it() or test() blocks.
Example:
it('returns a successful response', function () {
$this->get('/')->assertOk();
});
Why use Pest in Laravel?
✅ 1) Tests read better
You focus on the behavior (“it does X”), not the structure.
✅ 2) Less boilerplate
No need for class files and repeated method signatures for small tests.
✅ 3) Modern helpers
Datasets, expectations, plugins, and clean grouping.
✅ 4) Easy adoption
Because Pest runs on PHPUnit, you can migrate gradually.
Install Pest in Laravel
composer require pestphp/pest --dev
composer require pestphp/pest-plugin-laravel --dev
php artisan pest:install
Run tests:
php artisan test
# or
./vendor/bin/pest
Writing Your First Tests
Your first Laravel HTTP test (Pest)
it('shows the homepage', function () {
$this->get('/')
->assertOk()
->assertSee('Welcome');
});
Grouping tests
it('requires authentication', function () {
$this->get('/dashboard')->assertRedirect('/login');
})->group('auth');
Run only that group:
./vendor/bin/pest --group=auth
Database testing with factories
Pest plays nicely with Laravel’s RefreshDatabase trait.
use Illuminate\Foundation\Testing\RefreshDatabase;
use App\Models\User;
uses(RefreshDatabase::class);
it('creates a user', function () {
$user = User::factory()->create();
expect($user)->toBeInstanceOf(User::class);
$this->assertDatabaseHas('users', ['id' => $user->id]);
});
Expectation style (clean assertions)
Pest encourages expect() assertions that read fluently:
it('validates emails', function () {
$email = 'hello@example.com';
expect($email)
->toContain('@')
->toEndWith('.com');
});
You can still use classic PHPUnit assertions if you prefer.
Datasets: one test, multiple inputs
Datasets are great for validation rules, edge cases, and formatting logic.
it('rejects invalid emails', function ($email) {
$this->post('/register', [
'name' => 'John',
'email' => $email,
'password' => 'password',
'password_confirmation' => 'password',
])->assertSessionHasErrors('email');
})->with([
'no-at-symbol' => ['johnexample.com'],
'missing-domain' => ['john@'],
'empty' => [''],
]);
Laravel fakes (mail / queue) with Pest
Laravel provides “fakes” that make side effects easy to test.
Mail fake
use Illuminate\Support\Facades\Mail;
use App\Mail\WelcomeMail;
it('sends a welcome email', function () {
Mail::fake();
// trigger your logic here...
// $this->post('/register', [...])
Mail::assertSent(WelcomeMail::class);
});
Queue fake
use Illuminate\Support\Facades\Queue;
use App\Jobs\ProcessInvoice;
it('dispatches an invoice job', function () {
Queue::fake();
// trigger your logic here...
Queue::assertPushed(ProcessInvoice::class);
});
Migrating from PHPUnit to Pest (practical approach)
You don’t have to rewrite everything in one go.
Step 1: Install Pest
Keep your existing PHPUnit tests. Everything still runs.
Step 2: Write new tests in Pest style
Add new tests under tests/Feature or tests/Unit using it() blocks.
Step 3: Convert gradually
Convert the tests you touch often, or the ones with repetitive boilerplate.
PHPUnit (before)
class HomepageTest extends TestCase
{
public function test_homepage_is_ok(): void
{
$this->get('/')->assertOk();
}
}
Pest (after)
it('homepage is ok', function () {
$this->get('/')->assertOk();
});
Same behavior, less noise.
Best practices that matter (with Pest or PHPUnit)
- Prefer feature tests for business-critical flows (auth, checkout, CRUD).
- Use unit tests for pure logic (formatters, value objects, small services).
- Keep tests deterministic: fake external calls.
- Don’t test Laravel internals—test your behavior.
Quick checklist
- [ ] Install Pest + Laravel plugin
- [ ] Keep PHPUnit tests (no big bang migration)
- [ ] Write new tests in Pest style
- [ ] Use datasets for edge cases
- [ ] Fake Mail/Queue/Events to avoid side effects
- [ ] Convert old tests gradually
Wrap-up
Pest doesn’t replace PHPUnit—it makes PHPUnit feel nicer to write.
If you’ve avoided writing tests because they felt too verbose, Pest is a great way to lower the friction:
- less boilerplate,
- more readable tests,
- same reliability.
Try it on one feature test and see how quickly it becomes your default.
Top comments (0)