pestphp/pest
Pest is an elegant PHP testing framework focused on simplicity and a joyful developer experience. Write expressive tests with a clean syntax, run fast suites, and scale from tiny projects to large apps. Full docs at pestphp.com.
Installation:
composer require pestphp/pest --dev --with-all-dependencies
For Laravel projects, Pest is pre-installed in Laravel 10+.
First Test:
Replace tests/Feature/ExampleTest.php with:
use Pest\Test;
test('the application returns a successful response', function () {
$response = $this->get('/');
$response->assertOk();
});
Run Tests:
./vendor/bin/pest
toBeAuthenticated(), toHaveHttpStatus(), and toHaveValidationErrors() in the Laravel section.Test a Laravel API Endpoint:
test('create a new user', function () {
$response = $this->post('/api/register', [
'name' => 'John Doe',
'email' => 'john@example.com',
'password' => 'password',
]);
$response->assertCreated()
->assertJsonStructure(['data' => ['id', 'name', 'email']]);
});
Test Organization:
Use describe() for grouping related tests:
describe('User Authentication', function () {
it('fails with invalid credentials', function () {
$this->post('/login', ['email' => 'wrong@example.com', 'password' => 'wrong'])
->assertUnauthorized();
});
});
Data-Driven Testing:
Leverage with() for datasets:
test('validate user input', function (string $email, string $password) {
$this->post('/register', compact('email', 'password'))
->assertValidationErrors([$email ? 'email' : 'required', 'password']);
})->with([
['test@example.com', 'password'],
['', 'password'],
['test@example.com', ''],
]);
Mocking & Stubbing: Use Pest’s fluent syntax:
test('fetch user data from external API', function () {
$this->mock(\Illuminate\Http\Client\PendingRequest::class)
->shouldReceive('get')
->once()
->andReturn(['id' => 1, 'name' => 'John']);
$user = $this->get('/api/user/1')->json();
expect($user)->toBe(['id' => 1, 'name' => 'John']);
});
Browser Testing: Playwright-powered E2E tests:
test('user can login via browser', function () {
$this->browse()
->visit('/login')
->type('email', 'test@example.com')
->type('password', 'password')
->press('Login')
->assertPathIs('/dashboard');
});
Laravel Artisan Commands:
Test commands with $this->artisan():
test('run migrate command', function () {
$this->artisan('migrate:fresh')
->expectsOutput('Migrations run successfully.')
->assertExitCode(0);
});
Jobs & Queues:
Use $this->queue() helper:
test('dispatch and assert job', function () {
$this->queue(function () {
SendEmailJob::dispatch('test@example.com');
});
$this->assertQueued(SendEmailJob::class);
});
Database Transactions: Pest auto-rolls back transactions. For custom logic:
test('test with fresh database', function () {
$this->freshDatabase();
// Test logic here
});
Parallel Testing: Run tests in parallel for faster CI:
./vendor/bin/pest --parallel
Test Isolation:
Use beforeEach() and afterEach():
beforeEach(function () {
$this->actingAs(User::factory()->create());
});
afterEach(function () {
Cache::flush();
});
Snapshots Testing: Compare outputs against snapshots:
test('API response matches snapshot', function () {
$response = $this->get('/api/data');
expect($response->json())->toMatchSnapshot();
});
Architecture Testing: Enforce coding standards:
test('App\\Http\\Controllers should not use global functions', function () {
expect('App\Http\Controllers')
->not->toUseGlobalFunctions();
});
Flaky Test Handling: Mark flaky tests:
flaky('login sometimes fails due to race condition', function () {
$this->post('/login', [...])->assertOk();
});
Preset Conflicts:
Rules).use Pest\Laravel\Rules;
Parallel Testing Quirks:
--shard with time-based balancing:
./vendor/bin/pest --update-shards # Generate shard data
./vendor/bin/pest --shard=1/4 # Run shard 1 of 4
Browser Test Isolation:
->newBrowser() for isolated sessions:
test('parallel browser tests', function () {
$this->browse()->newBrowser()->visit('/');
});
Snapshot Mismatches:
--update-snapshots to regenerate:
./vendor/bin/pest --update-snapshots
CI-Specific Issues:
--teamcity flag may not work with Paratest.--teamcity with --parallel explicitly:
./vendor/bin/pest --parallel --teamcity
Verbose Output:
Run tests with -v for details:
./vendor/bin/pest -v
Test Filtering: Run specific tests:
./vendor/bin/pest Tests/Feature/AuthTest.php::test_login
Xdebug Integration:
Use --debug for Xdebug:
./vendor/bin/pest --debug
Coverage Exclusions: Exclude directories from coverage:
./vendor/bin/pest --coverage --exclude=tests/Feature/Browser/
Custom Test Directory:
Configure in pest.php:
return [
'stubs' => [
'test_case' => __DIR__.'/stubs/TestCase.php',
],
];
PHPUnit Integration:
Pest uses PHPUnit under the hood. Override PHPUnit config in phpunit.xml:
<php>
<env name="APP_ENV" value="testing"/>
</php>
Browser Test Headless Mode:
Disable headful mode in pest.php:
return [
'browser' => [
'headful' => false,
],
];
Custom Assertions:
Extend Pest’s assertions in tests/CreatesApplication.php:
use Pest\Expectation;
Expectation::extend('toBeEven', function (int $value) {
return $value % 2 === 0;
});
Preset Extensions: Add custom presets:
use Pest\Laravel\Laravel;
Laravel::macro('toBeAdmin', function () {
return $this->actingAs(User::factory()->admin()->create());
});
Plugin Development: Create custom plugins (e.g., for API testing):
// tests/Plugins/ApiTestPlugin.php
namespace Tests\Plugins;
use Pest\Plugin;
class ApiTestPlugin implements Plugin
{
public function register(): void
{
// Register custom test methods
}
}
How can I help you explore Laravel packages today?