Laravel testing skill

Write Pest feature tests with RefreshDatabase, mock external services, and create test data with Eloquent Factories in Laravel.

by HoangNguyen0403·MIT license·★ 569 Stars on the repo·GitHub ↗

Use now

Files of Laravel testing

HoangNguyen0403/develop1 file shown
SKILL.md
Show the full text61 lines

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)

1---
2name: laravel-testing
3description: 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.
4metadata:
5 triggers:
6 files:
7 - 'tests/**/*.php'
8 - 'phpunit.xml'
9 keywords:
10 - feature
11 - unit
12 - mock
13 - factory
14 - sqlite
15---
16# Laravel Testing
17 
18## **Priority: P1 (HIGH)**
19 
20## Core Rule Anchors
21 
22- **`[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.
23- **`[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.
24- **`[BE-TEST-03]` Ban on Shallow Assertions**: Never assert only `assertStatus(200)` without inspecting JSON response structures, payload fields, and invariants.
25- **`[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.
26- **`[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.
27 
28## Implementation Guidelines
29 
30- **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.
31- **Transactions**: For faster but non-truncating isolation, use **`DatabaseTransactions`**.
32- **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.
33- **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.
34- **HTTP Assertions (`[BE-TEST-03]`)**: Chain **`assertStatus(201)`**, **`assertJson(['data' => ...])`**, and `assertJsonStructure`. Avoid shallow assertions.
35- **Coverage**: Coverage is diagnostic and project-configured; verify risk-weighted critical paths rather than padding code for an arbitrary percentage.
36 
37## Anti-Patterns
38 
39- **`[BE-TEST-01]` Stylistic parameterized rewrites**: Do not demand dataset rewrites without a behavioral gap.
40- **`[BE-TEST-02]` Brittle DB mock string matching**: Do not match raw SQL strings in mocks; use SQLite in-memory or real DB.
41- **`[BE-TEST-03]` Shallow assertions**: Never assert status without verifying payload structures.
42- **`[BE-TEST-04]` Pass-through mock echoing**: Ban 1:1 mock echoing without contract assertions.
43- **`[BE-TEST-05]` Bug fix without reproduction test**: Ban bug fixes without a reproduction test.
44- **No real network calls**: Always mock or stub external services.
45- **No state leakage between tests**: Use `RefreshDatabase` trait.
46- **No `DB::table()->insert()`**: Never DB::table()->insert() raw data in tests — use Eloquent Factories instead.
47## References
48 
49- [Testing & Mocking Guide](references/implementation.md)
50 
51## Canonical response anchors
52 
53When this skill applies, preserve the following domain terminology or equivalent concrete examples in the answer when relevant:
54- DB_CONNECTION' value='sqlite
55- DB_DATABASE' value=':memory
56- DatabaseTransactions
57- assertJson(['data'
58- database rolled back
59- never DB::table()->insert()
60 
61- Additional task-grounded exact anchors: once()->with(100); assertStatus(201)

Discussion

Alternatives