laravel-testing
Auto-invoked skill
Comprehensive testing with Pest PHP - unit, feature, API, browser
Trigger Keywords
This skill automatically activates when Claude detects these keywords:
test
pest
coverage
tdd
assert
mock
Overview
The laravel-testing skill provides expertise for writing comprehensive tests using Pest PHP. It covers unit tests, feature tests, API tests, and browser tests with Laravel Dusk.
What This Skill Provides
- Pest PHP syntax - Modern, expressive test syntax with
describe(),it(),expect() - Factory patterns - Model factories with states and relationships
- Database testing - RefreshDatabase, transactions, assertions
- HTTP testing - Request/response testing with JSON assertions
- Mocking - Mockery, fakes, and dependency injection
- Coverage - Code coverage configuration and reporting
Example Conversations
# Writing tests for a feature
"Write tests for my OrderService that handles checkout and payment"
# Testing API endpoints
"Help me test my products API with authentication and validation"
# Mocking external services
"How do I mock the Stripe payment gateway in my tests?"
# Database assertions
"Test that deleting a user cascades to their posts"
Pest PHP Patterns
<?php
uses(RefreshDatabase::class);
describe('OrderService', function () {
beforeEach(function () {
$this->user = User::factory()->create();
$this->service = app(OrderService::class);
});
it('creates an order with valid items', function () {
$items = [
['product_id' => 1, 'quantity' => 2],
];
$order = $this->service->create($this->user, $items);
expect($order)
->toBeInstanceOf(Order::class)
->user_id->toBe($this->user->id)
->items->toHaveCount(1);
});
it('throws exception for empty cart', function () {
$this->service->create($this->user, []);
})->throws(EmptyCartException::class);
it('calculates total correctly', function () {
$order = Order::factory()
->has(OrderItem::factory()->count(3)->state(['price' => 10.00]))
->create();
expect($order->total)->toBe(30.00);
});
});
Test Types
| Type | Location | Use Case |
|---|---|---|
| Unit | tests/Unit/ |
Isolated class testing without framework |
| Feature | tests/Feature/ |
HTTP requests, full application stack |
| API | tests/Feature/Api/ |
JSON API endpoints with auth |
| Browser | tests/Browser/ |
Laravel Dusk end-to-end |
Testing Events, Jobs & Notifications
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Facades\Notification;
// Testing Events
it('dispatches OrderCreated event', function () {
Event::fake();
Order::factory()->create();
Event::assertDispatched(OrderCreated::class);
});
// Testing Jobs
it('queues SendOrderConfirmation job', function () {
Queue::fake();
$order = Order::factory()->create();
$order->confirm();
Queue::assertPushed(SendOrderConfirmation::class);
});
// Testing Notifications
it('sends confirmation notification', function () {
Notification::fake();
$user = User::factory()->create();
$order = Order::factory()->for($user)->create();
$order->sendConfirmation();
Notification::assertSentTo($user, OrderConfirmation::class);
});
// Testing Exceptions
it('throws exception for invalid state', function () {
$order = Order::factory()->delivered()->create();
expect(fn () => $order->cancel())
->toThrow(InvalidStateException::class);
});
Common Pitfalls
// 1. Not Using RefreshDatabase - Always reset database state
uses(RefreshDatabase::class);
// 2. Testing Implementation - Test behavior, not implementation
// BAD - testing internal method
expect($service->calculateInternally())->toBe(100);
// GOOD - testing behavior
expect($service->getTotal())->toBe(100);
// 3. Slow Tests - Mock external services
$mock = Mockery::mock(PaymentGateway::class);
$mock->shouldReceive('charge')->once()->andReturn(true);
app()->instance(PaymentGateway::class, $mock);
// 4. Missing Edge Cases - Test boundaries and errors
it('rejects negative quantities', function () {
$this->postJson('/api/orders', ['quantity' => -1])
->assertUnprocessable();
});
// 5. No Assertions - Every test needs clear assertions
// BAD
it('does something', function () {
$service->process(); // No assertion!
});
// 6. Shared State - Use beforeEach for isolation
beforeEach(function () {
$this->user = User::factory()->create();
});
Package Integration
- pestphp/pest - Testing framework
- pestphp/pest-plugin-laravel - Laravel helpers
- mockery/mockery - Mocking library
- laravel/dusk - Browser testing
Best Practices
- One assertion concept per test
- Use descriptive test names
- Test edge cases and errors
- Keep tests fast (mock slow operations)
- Use factories for test data
- Follow Arrange-Act-Assert pattern
Testing HTTP Responses
it('returns products with pagination', function () {
Product::factory()->count(25)->create();
$response = $this->getJson('/api/products');
$response
->assertOk()
->assertJsonCount(15, 'data')
->assertJsonStructure([
'data' => [['id', 'name', 'price']],
'meta' => ['total', 'per_page'],
]);
});
it('validates required fields', function () {
$this->actingAs(User::factory()->create())
->postJson('/api/products', [])
->assertUnprocessable()
->assertJsonValidationErrors(['name', 'price']);
});
Related Commands
- /laravel-agent:test:make - Generate tests for a class
- /laravel-agent:test:coverage - Run tests with coverage
Related Agent
- laravel-testing - Generates comprehensive test suites