diff --git a/.ai/pest/3/skill/pest-testing/SKILL.blade.php b/.ai/pest/3/skill/pest-testing/SKILL.blade.php index b2486359..7a9b054f 100644 --- a/.ai/pest/3/skill/pest-testing/SKILL.blade.php +++ b/.ai/pest/3/skill/pest-testing/SKILL.blade.php @@ -100,9 +100,28 @@ Pest 3 provides improved type coverage analysis. Run with `--type-coverage` flag. +## Performance + +### Database + +- **Prefer `LazilyRefreshDatabase` over `RefreshDatabase`.** Tests that never touch the DB skip the migration/truncation cost entirely. Only use `RefreshDatabase` when the test specifically needs fresh migrations. +- **Use `$factory->recycle($model)`** to create a shared parent once and thread it through nested factory calls, instead of each nested factory creating its own parent. +- **Create only the models the test needs.** Audit factory usage — avoid nested `create()` calls that spawn extra rows the test never inspects. + +### Faking + +Fake any external interaction that is not the subject of the test: + +- `Notification::fake()`, `Mail::fake()`, `Bus::fake()`, `Event::fake()`, `Queue::fake()`. +- If the test hits an HTTP endpoint that dispatches jobs, fake the queue unless the test is asserting job behavior — otherwise jobs execute inline and pull in everything they touch. + +For project-level environment and CI optimizations, see [reference/performance.md](reference/performance.md). + ## Common Pitfalls - Not importing `use function Pest\Laravel\mock;` before using mock - Using `assertStatus(200)` instead of `assertSuccessful()` - Forgetting datasets for repetitive validation tests - Deleting tests without approval +- Enabling `RefreshDatabase` on every test even when the test does not hit the DB +- Adding `Queue::fake()` but still asserting side effects of the job (those will never happen) diff --git a/.ai/pest/3/skill/pest-testing/reference/performance.md b/.ai/pest/3/skill/pest-testing/reference/performance.md new file mode 100644 index 00000000..d68da01d --- /dev/null +++ b/.ai/pest/3/skill/pest-testing/reference/performance.md @@ -0,0 +1,39 @@ +# Test Performance Reference + +Environment and tooling optimizations for faster Laravel test suites. These are project-level or CI-level settings — not per-test code changes. + +## Environment and config + +- **Set `BCRYPT_ROUNDS=4`** in `.env.testing` (or `phpunit.xml`). Default is 12 and hashing dominates auth tests. +- **Disable XDebug.** Disable pcov too at scale unless you specifically need coverage. +- **Disable background packages** in the testing environment: Pulse, Telescope, Nightwatch, and similar 3rd-party packages that do work on every request/command. +- **Use `WithCachedConfig` and `WithCachedRoutes` traits** to avoid re-parsing config and routes on every test. +- **Call `withoutVite()` (or `withoutMix()`)** in your test setup so the framework does not try to resolve built assets. +- **`Http::preventingStrayRequests()`** in your base `Pest.php`. A single slow stray request can punish every test. Note this only catches requests made through Laravel's HTTP client. Audit direct Guzzle / cURL usage separately. +- **`Sleep::fake(syncWithCarbon: true)`** in your base `Pest.php` so retries and backoffs do not actually sleep. +- **`Exceptions::fake()`** to make sure you are not reporting to Flare/Sentry/Bugsnag from within tests. + +## Profile and diagnose + +- **`./vendor/bin/pest --profile`** prints the slowest tests per shard. Start there. Patterns in the top-10 often apply suite-wide. +- When a slow test is mysterious, instrument event listeners or add temporary logging to find the unexpected work happening behind the scenes. + +## Quick reference + +| Area | Action | +|------|--------| +| Hashing | `BCRYPT_ROUNDS=4` | +| Debuggers | Disable XDebug, disable pcov | +| Background pkgs | Disable Pulse, Telescope, Nightwatch | +| Config / routes | `WithCachedConfig`, `WithCachedRoutes` | +| Assets | `withoutVite()` / `withoutMix()` | +| HTTP | `Http::preventingStrayRequests()` | +| Sleep | `Sleep::fake(syncWithCarbon: true)` | +| Exceptions | `Exceptions::fake()` | +| Profiling | `pest --profile` | + +## Common environment mistakes + +- Running the suite with XDebug loaded "just in case" a test fails. +- Leaving `BCRYPT_ROUNDS` at the default because `.env.testing` was never created. +- Calling real `sleep()` or using Carbon in production code, which prevents `Sleep::fake()` from helping. diff --git a/.ai/pest/4/skill/pest-testing/SKILL.blade.php b/.ai/pest/4/skill/pest-testing/SKILL.blade.php index 0b8225ac..86cc13a8 100644 --- a/.ai/pest/4/skill/pest-testing/SKILL.blade.php +++ b/.ai/pest/4/skill/pest-testing/SKILL.blade.php @@ -146,6 +146,23 @@ ->toHaveSuffix('Controller'); @endboostsnippet +## Performance + +### Database + +- **Prefer `LazilyRefreshDatabase` over `RefreshDatabase`.** Tests that never touch the DB skip the migration/truncation cost entirely. Only use `RefreshDatabase` when the test specifically needs fresh migrations. +- **Use `$factory->recycle($model)`** to create a shared parent once and thread it through nested factory calls, instead of each nested factory creating its own parent. +- **Create only the models the test needs.** Audit factory usage — avoid nested `create()` calls that spawn extra rows the test never inspects. + +### Faking + +Fake any external interaction that is not the subject of the test: + +- `Notification::fake()`, `Mail::fake()`, `Bus::fake()`, `Event::fake()`, `Queue::fake()`. +- If the test hits an HTTP endpoint that dispatches jobs, fake the queue unless the test is asserting job behavior — otherwise jobs execute inline and pull in everything they touch. + +For project-level environment and CI optimizations, see [reference/performance.md](reference/performance.md). + ## Common Pitfalls - Not importing `use function Pest\Laravel\mock;` before using mock @@ -153,3 +170,5 @@ - Forgetting datasets for repetitive validation tests - Deleting tests without approval - Forgetting `assertNoJavaScriptErrors()` in browser tests +- Enabling `RefreshDatabase` on every test even when the test does not hit the DB +- Adding `Queue::fake()` but still asserting side effects of the job (those will never happen) diff --git a/.ai/pest/4/skill/pest-testing/reference/performance.md b/.ai/pest/4/skill/pest-testing/reference/performance.md new file mode 100644 index 00000000..d68da01d --- /dev/null +++ b/.ai/pest/4/skill/pest-testing/reference/performance.md @@ -0,0 +1,39 @@ +# Test Performance Reference + +Environment and tooling optimizations for faster Laravel test suites. These are project-level or CI-level settings — not per-test code changes. + +## Environment and config + +- **Set `BCRYPT_ROUNDS=4`** in `.env.testing` (or `phpunit.xml`). Default is 12 and hashing dominates auth tests. +- **Disable XDebug.** Disable pcov too at scale unless you specifically need coverage. +- **Disable background packages** in the testing environment: Pulse, Telescope, Nightwatch, and similar 3rd-party packages that do work on every request/command. +- **Use `WithCachedConfig` and `WithCachedRoutes` traits** to avoid re-parsing config and routes on every test. +- **Call `withoutVite()` (or `withoutMix()`)** in your test setup so the framework does not try to resolve built assets. +- **`Http::preventingStrayRequests()`** in your base `Pest.php`. A single slow stray request can punish every test. Note this only catches requests made through Laravel's HTTP client. Audit direct Guzzle / cURL usage separately. +- **`Sleep::fake(syncWithCarbon: true)`** in your base `Pest.php` so retries and backoffs do not actually sleep. +- **`Exceptions::fake()`** to make sure you are not reporting to Flare/Sentry/Bugsnag from within tests. + +## Profile and diagnose + +- **`./vendor/bin/pest --profile`** prints the slowest tests per shard. Start there. Patterns in the top-10 often apply suite-wide. +- When a slow test is mysterious, instrument event listeners or add temporary logging to find the unexpected work happening behind the scenes. + +## Quick reference + +| Area | Action | +|------|--------| +| Hashing | `BCRYPT_ROUNDS=4` | +| Debuggers | Disable XDebug, disable pcov | +| Background pkgs | Disable Pulse, Telescope, Nightwatch | +| Config / routes | `WithCachedConfig`, `WithCachedRoutes` | +| Assets | `withoutVite()` / `withoutMix()` | +| HTTP | `Http::preventingStrayRequests()` | +| Sleep | `Sleep::fake(syncWithCarbon: true)` | +| Exceptions | `Exceptions::fake()` | +| Profiling | `pest --profile` | + +## Common environment mistakes + +- Running the suite with XDebug loaded "just in case" a test fails. +- Leaving `BCRYPT_ROUNDS` at the default because `.env.testing` was never created. +- Calling real `sleep()` or using Carbon in production code, which prevents `Sleep::fake()` from helping.