pestphp/pest
Pest is an elegant PHP testing framework focused on simplicity and joyful, expressive tests. Built for modern PHP projects, it offers a clean syntax, rich assertions, plugins, and great developer experience for unit, feature, and more.
Installation:
composer require pestphp/pest --dev --with-all-dependencies
For Laravel projects, Pest is pre-configured via pest.php (auto-generated in tests/).
First Test:
Replace tests/Feature/ExampleTest.php with:
use App\Models\User;
it('creates a user', function () {
$user = User::factory()->create();
expect($user->email)->toBeString();
});
Run with:
./vendor/bin/pest
Key Files:
pest.php: Global configuration (plugins, aliases, parallelization).tests/: Test files (Pest auto-discovers .php files).phpunit.xml: Fallback config (rarely modified).Replace a Laravel HttpTestCase with Pest’s fluent syntax:
it('returns a welcome page', function () {
$response = $this->get('/');
$response->assertStatus(200)
->assertSee('Welcome');
});
tests/
├── Unit/ # Unit tests (e.g., `UserTest.php`)
├── Feature/ # HTTP/API tests (e.g., `AuthTest.php`)
├── Browser/ # Playwright E2E tests (e.g., `CheckoutTest.php`)
└── Pest.php # Global config
it() for scenarios, describe() for grouping:
describe('User Authentication', function () {
it('fails with invalid credentials');
it('redirects after login');
});
Helpers:
actingAs(): Simulate logged-in users.create(): Factory shortcuts (e.g., User::factory()->create()).assertSee(), assertDontSee(): Response assertions.it('requires authentication', function () {
$this->get('/dashboard')
->assertRedirect('/login');
});
Service Providers:
Use TestCase traits or extend PestTestCase:
use Pest\TestCase;
class UserTest extends PestTestCase {
// Tests here
}
composer require pestphp/pest-plugin-browser
it('submits a form', function () {
$this->browse()
->visit('/login')
->fill('email', 'user@example.com')
->fill('password', 'secret')
->press('Login')
->assertPathIs('/dashboard');
});
$this->browse()->from()->newYork()->visit('/');
composer require pestphp/pest-plugin-arch
it('prevents controllers from calling databases directly')
->expect('App\Http\Controllers')
->not->toUse('App\Models')
->but->toUse('App\Services');
pest.php):
parallel()->with(4); // Run 4 parallel workers
--parallel flag:
./vendor/bin/pest --parallel --workers=8
$mock = $this->mock(EmailService::class);
$mock->shouldReceive('send')->once();
$this->stub(Storage::class, function ($stub) {
$stub->shouldReceive('disk')->andReturnSelf();
$stub->shouldReceive('put')->andReturn(true);
});
$user = User::factory()->create(['email' => 'test@example.com']);
$data = [
'title' => 'Test Post',
'body' => 'Content...',
];
it('uses raw SQL', function () {
DB::beginTransaction();
// Test logic
DB::rollBack();
})->withoutTransactions();
Playwright Browser Tests:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD is unset in CI.->waitForSelector() sparingly; prefer ->assertPathIs() for faster checks.newYork() require intl PHP extension.Architecture Testing:
->ignore('Vendor\\*')
Parallelization:
beforeEach() for setup.--workers=2 as a fallback.Database Testing:
php artisan migrate in beforeAll() if needed.DatabaseMigrationsAndSeeders::class trait or ->withSeed():
it('uses seeded data')->withSeed();
Assertion Quirks:
expect(1.0000000001)->toBeApproximately(1, 3).expect($users)->toHaveCount(1)
->first()
->email->toBe('test@example.com');
./vendor/bin/pest --verbose
->withoutMocking() or ->withoutStubs() to debug mock/stub issues.--headed to see Playwright in action (useful for visual regression testing).pest.php Overrides:
default() to customize global behavior:
default()
->in('tests/Unit')
->withTimer();
expect() to modify assertions globally:
expect()->extend('custom', function ($actual, $expected) {
return $actual === $expected;
});
Plugin Conflicts:
pest.php:
plugins([
// Disable browser plugin
// \Pest\Browser\BrowserPlugin::class,
]);
CI-Specific Issues:
.env.ci):
XDEBUG_MODE=off
./vendor/bin/pest --memory=1G
Custom Assertions:
use Pest\Expectation;
Expectation::extend('isEven', function ($actual) {
return $actual % 2 === 0;
});
Usage:
expect(4)->toBeEven();
Test Templates:
Use pest --init to scaffold custom templates (e.g., FeatureTest.php):
// tests/_templates/FeatureTest.php
<?php
use Pest\TestCase;
class FeatureTest extends TestCase {
use CreatesApplication;
// Custom setup
}
Browser Customization:
Override Playwright config in pest.php:
browser()
->withOptions([
'timeout' => 10000,
'headless' => false,
]);
Parallel Worker Hooks: Run code before/after parallel workers:
parallel()
->before(function () {
// Shared setup
})
->after(function () {
// Shared teardown
});
./vendor/bin/pest --filter="slow"
./vendor/bin/pest tests/Feature/AuthTest.php
./vendor/bin/pest --
How can I help you explore Laravel packages today?