name: laravel-testing
description: Write Pest feature tests with RefreshDatabase, mock external services, and create test data with Eloquent Factories in Laravel. Use when adding HTTP tests, configuring SQLite in-memory test database, or mocking payment services.
metadata:
triggers:
files:
- 'tests/**/*.php'
- 'phpunit.xml'
keywords:
- feature
- unit
- mock
- factory
- sqlite
Laravel Testing
Priority: P1 (HIGH)
Core Rule Anchors
[BE-TEST-01] Parameterized Tests for Equivalent Cases: Prefer dataset in Pest or PHPUnit data providers for equivalent inputs and boundary permutations. Distinct scenarios may stay separate test methods. Reviewers must not demand parameterized rewrites without a behavioral gap.
[BE-TEST-02] Ban on Brittle DB Mock String Matching: Do not use string-matching mocks for raw SQL queries. Test pure domain calculations directly, or use SQLite in-memory / real DB for repository verification.
[BE-TEST-03] Ban on Shallow Assertions: Never assert only assertStatus(200) without inspecting JSON response structures, payload fields, and invariants.
[BE-TEST-04] Ban on Pass-Through Interface Mocks: Repositories and service handlers must test contract compliance and error mapping. Ban 1:1 pass-through mock echoing without contract assertions.
[BE-TEST-05] Bug-First Regression Lock: Every PR fixing a bug ticket or with title fix(...) must introduce a test reproducing the defect prior to the fix.
Implementation Guidelines
- Pest Feature Tests: Use
uses(RefreshDatabase::class) at top of Pest files. Example: it('creates post', fn() => $this->postJson('/api/posts', [...]) verifies database rolled back after each test.
- Transactions: For faster but non-truncating isolation, use
DatabaseTransactions.
- Service Mocking (
[BE-TEST-04]): Use $this->mock(PaymentService::class) with shouldReceive('charge')->once()->with(100) to assert interaction. Use $this->spy() for loose verification. Never make real network calls. Ban 1:1 pass-through mock echoing.
- Factories & DB (
[BE-TEST-02]): Create test data via Post::factory()->count(3)->create(['user_id' => $id]). In phpunit.xml, set DB_CONNECTION' value='sqlite' and DB_DATABASE' value=':memory:' for in-memory tests.
- HTTP Assertions (
[BE-TEST-03]): Chain assertStatus(201), assertJson(['data' => ...]), and assertJsonStructure. Avoid shallow assertions.
- Coverage: Coverage is diagnostic and project-configured; verify risk-weighted critical paths rather than padding code for an arbitrary percentage.
Anti-Patterns
[BE-TEST-01] Stylistic parameterized rewrites: Do not demand dataset rewrites without a behavioral gap.
[BE-TEST-02] Brittle DB mock string matching: Do not match raw SQL strings in mocks; use SQLite in-memory or real DB.
[BE-TEST-03] Shallow assertions: Never assert status without verifying payload structures.
[BE-TEST-04] Pass-through mock echoing: Ban 1:1 mock echoing without contract assertions.
[BE-TEST-05] Bug fix without reproduction test: Ban bug fixes without a reproduction test.
- No real network calls: Always mock or stub external services.
- No state leakage between tests: Use
RefreshDatabase trait.
- No
DB::table()->insert(): Never DB::table()->insert() raw data in tests — use Eloquent Factories instead.
References
Canonical response anchors
When this skill applies, preserve the following domain terminology or equivalent concrete examples in the answer when relevant:
-
DB_CONNECTION' value='sqlite
-
DB_DATABASE' value=':memory
-
DatabaseTransactions
-
assertJson(['data'
-
database rolled back
-
never DB::table()->insert()
-
Additional task-grounded exact anchors: once()->with(100); assertStatus(201)