| name | pesttesting |
| description | Laravel testing conventions using the Pest PHP framework. Covers test structure, AAA pattern, HTTP assertions, datasets, mocking, browser tests, and architecture tests. |
| compatible_agents | ["test","refactor","review"] |
Pest Testing
When to Use
- Writing new tests in projects that standardize on Pest syntax.
- Refactoring older tests into fluent, readable behavior descriptions.
- Adding datasets, architecture tests, and expressive HTTP assertions.
When Not to Use
- Existing areas intentionally locked to class-based PHPUnit style.
- Browser tests without real browser requirements (use feature tests instead).
- Non-test automation scripts where Pest semantics do not apply.
Preconditions
- Pest is installed and configured for Laravel (
vendor/bin/pest works).
- Test bootstrap and DB setup are available (
phpunit.xml/env test config).
- Team conventions for test file placement and naming are understood.
- Fakes/mocks strategy is clear for external integrations.
Process Checklist
Rules
- Use Pest syntax consistently for new and refactored tests.
- Use named HTTP assertions, not raw numeric status checks.
- Prefer Laravel fakes for framework integrations (events, mail, queue, notifications).
- Keep browser-test constraints aligned with Dusk guidance.
- Never delete tests without explicit approval.
Examples
uses(RefreshDatabase::class);
it('creates an invoice for the given order', function () {
$order = Order::factory()->create();
$response = $this->postJson('/api/invoices', ['order_id' => $order->id]);
$response->assertCreated();
expect(Invoice::count())->toBe(1);
});
describe('InvoiceController', function () {
beforeEach(function () {
$this->user = User::factory()->create();
$this->actingAs($this->user);
});
it('lists all invoices', function () {
Invoice::factory()->count(3)->create();
$this->getJson('/api/invoices')->assertSuccessful();
});
it('creates an invoice', function () {
$order = Order::factory()->create();
$this->postJson('/api/invoices', ['order_id' => $order->id])
->assertCreated();
});
});
it('rejects invalid email addresses', function (string $email) {
$this->postJson('/api/users', ['email' => $email])
->assertUnprocessable();
})->with([
'empty string' => [''],
'missing @' => ['notanemail'],
'missing tld' => ['user@domain'],
]);
$response->assertSuccessful();
$response->assertCreated();
$response->assertNoContent();
$response->assertUnprocessable();
$response->assertForbidden();
$response->assertUnauthorized();
$response->assertNotFound();
arch('no debug calls in production code')
->expect('App')
->not->toUse(['dd', 'dump', 'var_dump', 'ray']);
arch('controllers have the correct suffix')
->expect('App\Http\Controllers')
->toHaveSuffix('Controller');
uses(\Illuminate\Foundation\Testing\DatabaseTruncation::class);
it('submits invoice flow in browser', function () {
$this->browse(function (Browser $browser) {
$browser->visit('/invoices/create')
->assertNoJavaScriptErrors()
->waitFor('@submit-button');
});
});
Run Commands
php artisan test
vendor/bin/pest
vendor/bin/pest tests/Feature/InvoiceControllerTest.php
vendor/bin/pest --filter="creates an invoice"
vendor/bin/pest --group=feature
Testing Guidance
- Keep tests behavior-focused and easy to read at a glance.
- Use datasets and
beforeEach() to reduce duplication before adding new assertions.
- For browser behavior, defer selector/waiting details to
Dusk/SKILL.md.
Anti-Patterns
- Using
assertStatus(200) instead of named assertion methods
- Using
RefreshDatabase in browser (Dusk) tests — use DatabaseTruncation
- Using
pause(3000) in browser tests — use waitFor() or waitForText()
- Repeating the same test for multiple inputs instead of using datasets
- Using CSS class or ID selectors in Dusk — use
dusk="" attributes
- Missing
uses(RefreshDatabase::class) in feature/unit files that touch the database
- Deleting a test without explicit approval
- Missing
assertNoJavaScriptErrors() after visit() in browser tests
References