Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message
Code Testing General
pekral/arch-app-services
cursor
pest
flaky-tests
database-isolation
factories
time-freezing
queues
mocking

Shared testing conventions for skills that generate or modify tests

Install
php artisan boost:add-skill pekral/arch-app-services

Save this content to: .cursor/rules/code-testing/general.mdc

## Testing Rules

- Follow test conventions from:
    - @rules/php/core-standards.mdc
    - @rules/laravel/architecture.mdc (for Laravel projects)

- Write all new tests using Pest syntax (`it()` / `test()` functional blocks). Do not introduce PHPUnit-style class-based tests for new test files.
- When modifying existing PHPUnit tests, prefer rewriting them to Pest via `@skills/rewrite-tests-pest/SKILL.md` rather than extending the legacy class.
- Never use `describe()`; use top-level `it()` / `test()` only.
- Test classes should be `final`.
- Prefer local variables; avoid shared mutable state.
- In Laravel Pest projects, define `uses(Tests\TestCase::class)` in `tests/Pest.php` instead of repeating it in every test file.

## Flaky Test Prevention
A flaky test fails inconsistently, is hard to reproduce, and is often "fixed" by simply re-running the pipeline. Flaky tests destroy trust in CI — once a team learns to re-run instead of investigate, it starts ignoring real failures too. Every new or modified test must be deterministic: it must pass repeatedly, in isolation, and in any order. Apply the following:

- **Freeze time; never assert on the wall clock.** Tests that read `now()`, `time()`, `sleep()`, or the ambient timezone fail depending on environment speed and clock drift. Pin time with `Carbon::setTestNow(...)` (or `travelTo()`) and always restore it afterwards — `Carbon::setTestNow()` / `travelBack()` in `afterEach`.
- **Isolate database state.** A test must never rely on rows created by another test. Assertions such as `assertDatabaseCount('posts', 1)` start failing the moment another test seeds the same table. Isolate with `RefreshDatabase` (or `DatabaseTransactions`) and create exactly the rows the test under assertion needs.
- **Make factory data deterministic.** Do not let random factory values decide the outcome. Explicitly set every attribute the assertion depends on (for example the user's role) instead of trusting factory defaults or random states.
- **Test queues and async jobs deterministically.** Never wait for an asynchronous job to finish before asserting. Either assert the dispatch with `Bus::fake()` / `Queue::fake()`, or force `QUEUE_CONNECTION=sync` in the test environment so jobs run inline.
- **Keep shared resources parallel-safe.** Parallel runs do not create flakiness — they expose existing shared state: Redis keys, files, temp directories, config cache, static properties, and singletons. Isolate per test with `Storage::fake()`, unique keys / file names, and by avoiding mutable static or singleton state.
- **Never call external services directly.** Real HTTP requests fail on network issues, rate limits, latency, or sandbox outages — fake every outbound call with `Http::fake()` (and the relevant SDK fakes). See the *External Calls* section below for the full no-network / no-DNS contract.
- **Guarantee order independence.** A test must pass on its own, repeatedly, and in any order. If a test passes only because another test ran before it, fix the hidden dependency — do not rely on suite ordering.

## Data Handling
- For Laravel:
    - Use `Model::factory()` for persisted data.
    - Do not duplicate database defaults unless explicitly required by the test.
- Avoid mocking data that can be created via factories.

## Mocking
- Prefer storing real data in the database over mocking ModelManager or Repository classes.
- Mock only:
    - external services that cannot run in test environment
    - exception scenarios that are hard to reach otherwise
- Prefer partial mocks over full mocks.
- Remove unnecessary or redundant mocks.

## Jobs
- Dispatch jobs using `JobClass::dispatch(...)` only.
- Assert a job was dispatched with `Queue::assertPushed(JobClass::class)` (optionally with an integer times count, `Queue::assertPushed(JobClass::class, $times)`) only — never pass a closure/callback argument (issue #756). The assertion's sole purpose is to verify the job was pushed onto the queue; a closure inspecting job properties belongs in a dedicated unit test of the job itself, not in the dispatch assertion. This is a CR finding under **Queue assertion specificity (issue #756)** in `@rules/code-review/general.mdc` Core Analysis Walk-through — raise it there once, never additionally under Strict rule compliance.

## External Calls
- Tests must not call external services.
- Mock all HTTP requests.
- Use local or in-memory database connections for tests.
- Avoid DNS lookups in tests.

## Consistency
- Ensure new or modified tests follow existing project conventions.

## Test Organization
- Place every new test file under a directory path that mirrors the namespace of the production class it covers (e.g. `App\Service\Billing\InvoiceCalculator` → `tests/Service/Billing/InvoiceCalculatorTest.php`, `Pekral\CursorRules\InstallerPath` → `tests/InstallerPathTest.php`). A reviewer must be able to walk from a class to its tests by walking the parallel folder tree without searching.
- One production class maps to one primary test file. The file name is `{ClassName}Test.php` (Pest functional test file using the same base name as the SUT plus the `Test` suffix). Helper / scenario test files extracted from a primary test file keep the same directory and a descriptive `{ClassName}{Scenario}Test.php` name so the relationship stays explicit.
- Cross-cutting tests that do not target a single production class (full-stack feature flows, contract tests for an external API, install / boot scripts) go under an intent-named directory at the top of `tests/` (`tests/Feature/<flow>`, `tests/Contract/<vendor>`, `tests/Integration/<area>`). Do not park them next to an unrelated class just to satisfy a parallel path.
- Group multiple `it()` / `test()` blocks for the same SUT in the same test file when they share setup and subject. Do not split one SUT across many files unless the per-file size genuinely hurts readability (then use the `{ClassName}{Scenario}Test.php` form above).
- Each `it()` / `test()` description states the scenario in plain language and matches what the body actually asserts. Examples of *matching* descriptions: `it('returns zero for an empty cart')`, `test('throws InvalidArgumentException when the discount is negative')`. Examples of *non-matching* descriptions that violate this rule: generic placeholders (`it('it works')`, `test('test1')`, `test('happy path')`), descriptions that name the method instead of the scenario (`test('calculate')`, `it('handles getUser')`), or descriptions that contradict the assertions (`it('adds user')` over a body that asserts removal). When changing what a test asserts, rename the description in the same change.

## Comments in Tests
- **A test carries even less comment budget than production code.** The `it()` / `test()` description already states the scenario, arrange-act-assert already states the shape, and the assertions already state the expectation — so a comment narrating the setup, labelling the three phases (`// arrange`), or paraphrasing the description is pure noise. Do not write it, and delete it when you find it in a test you are already changing, per `@rules/php/core-standards.mdc` *Documentation*.
- **Keep only what the test body cannot say:** why a fixture holds an odd-looking value, the external quirk or upstream bug a regression pins, the issue / spec the case reproduces, or a genuinely non-obvious reason the assertion looks the way it does. Everything else belongs in the test description — when a comment is needed to tell the reader *what* the test does, rename the description instead of writing the comment.
- **A comment that survives is maintained.** When you change what a test asserts, update or delete its comment in the same change — a comment describing an assertion the test no longer makes misleads the next reader exactly as a stale production docblock does.

## Test Organization Review Hook
- In code-review and pre-push contexts, every new or moved test file must satisfy the **Test Organization** rules above. Misplaced files, mismatched file names, and descriptions that do not match the asserted scenario are CR findings — see `@skills/code-review/SKILL.md` "Test organization" Core Analysis bullet for the severity matrix and Suggested Fix template.

## Scope
- Focus tests on behavior, not implementation details.

## Execution
- Run tests in parallel whenever possible (e.g. `php artisan test --parallel` or `pest --parallel`).

## Quality
- Prefer simple, readable tests over complex setups.
- Use data providers (datasets) where they improve readability and reduce duplication across similar test cases.
- Tests must not contain conditions (e.g., `if`, `switch`); split conditional logic into separate test cases or data providers instead.

## Coverage
- Every test change must be verified to be functional — run affected tests after each modification.
- Require 100% code coverage for every changed or added code path — applies equally to code modifications and code review.
- Before running coverage, discover the project's coverage command (prefer Phing target from `build.xml`/`phing.xml`; fall back to a Composer script in `composer.json` such as `test:coverage` or `coverage`). Do not assume a default command.
- **Coverage reporting is short by default (issue #528 follow-up).** Run the coverage check on every change, but report the result on the published CR / tracker comment **only** when there is something the reader must act on:
    - **uncovered changed lines** — list every uncovered line as a Critical finding and render the `## Coverage` section with the tool, exact command, and the uncovered-line list;
    - **coverage tooling unavailable** — raise the missing-tool case as a Critical finding and render the `## Coverage` section with the reason in place of a result.
  When every changed line is at 100% coverage and the tool ran successfully, **omit the `## Coverage` section entirely, omit the `Coverage:` header line, and omit the `coverage …` slot from the final summary line.** The CR is "clean" on the Counts line and the omission is the signal that coverage is satisfied — never emit `100%` / `clean` / `n/a` placeholders for the section, the header line, or the summary slot. The coverage check itself still runs unconditionally on every CR; only the user-visible reporting is short-circuited.

## Code Style and Quality Gates
- Discover available fixers and checkers: prefer Phing targets (`build.xml`/`phing.xml`) over Composer scripts (`composer.json`).
- Run available fixers on changed test files and fix any violations.
- Run available checkers/analyzers on changed test files and resolve all reported errors **by rewriting the flagged code, never by adding a suppression annotation** (`@rules/php/core-standards.mdc` PHP Practices).
- Complete all quality gates before finalizing test changes.

## Test Review
- After completing test changes, run a quick code review focused on test quality against these rules.

Testing Rules

  • Follow test conventions from:

    • @rules/php/core-standards.mdc
    • @rules/laravel/architecture.mdc (for Laravel projects)
  • Write all new tests using Pest syntax (it() / test() functional blocks). Do not introduce PHPUnit-style class-based tests for new test files.

  • When modifying existing PHPUnit tests, prefer rewriting them to Pest via @skills/rewrite-tests-pest/SKILL.md rather than extending the legacy class.

  • Never use describe(); use top-level it() / test() only.

  • Test classes should be final.

  • Prefer local variables; avoid shared mutable state.

  • In Laravel Pest projects, define uses(Tests\TestCase::class) in tests/Pest.php instead of repeating it in every test file.

Flaky Test Prevention

A flaky test fails inconsistently, is hard to reproduce, and is often "fixed" by simply re-running the pipeline. Flaky tests destroy trust in CI — once a team learns to re-run instead of investigate, it starts ignoring real failures too. Every new or modified test must be deterministic: it must pass repeatedly, in isolation, and in any order. Apply the following:

  • Freeze time; never assert on the wall clock. Tests that read now(), time(), sleep(), or the ambient timezone fail depending on environment speed and clock drift. Pin time with Carbon::setTestNow(...) (or travelTo()) and always restore it afterwards — Carbon::setTestNow() / travelBack() in afterEach.
  • Isolate database state. A test must never rely on rows created by another test. Assertions such as assertDatabaseCount('posts', 1) start failing the moment another test seeds the same table. Isolate with RefreshDatabase (or DatabaseTransactions) and create exactly the rows the test under assertion needs.
  • Make factory data deterministic. Do not let random factory values decide the outcome. Explicitly set every attribute the assertion depends on (for example the user's role) instead of trusting factory defaults or random states.
  • Test queues and async jobs deterministically. Never wait for an asynchronous job to finish before asserting. Either assert the dispatch with Bus::fake() / Queue::fake(), or force QUEUE_CONNECTION=sync in the test environment so jobs run inline.
  • Keep shared resources parallel-safe. Parallel runs do not create flakiness — they expose existing shared state: Redis keys, files, temp directories, config cache, static properties, and singletons. Isolate per test with Storage::fake(), unique keys / file names, and by avoiding mutable static or singleton state.
  • Never call external services directly. Real HTTP requests fail on network issues, rate limits, latency, or sandbox outages — fake every outbound call with Http::fake() (and the relevant SDK fakes). See the External Calls section below for the full no-network / no-DNS contract.
  • Guarantee order independence. A test must pass on its own, repeatedly, and in any order. If a test passes only because another test ran before it, fix the hidden dependency — do not rely on suite ordering.

Data Handling

  • For Laravel:
    • Use Model::factory() for persisted data.
    • Do not duplicate database defaults unless explicitly required by the test.
  • Avoid mocking data that can be created via factories.

Mocking

  • Prefer storing real data in the database over mocking ModelManager or Repository classes.
  • Mock only:
    • external services that cannot run in test environment
    • exception scenarios that are hard to reach otherwise
  • Prefer partial mocks over full mocks.
  • Remove unnecessary or redundant mocks.

Jobs

  • Dispatch jobs using JobClass::dispatch(...) only.
  • Assert a job was dispatched with Queue::assertPushed(JobClass::class) (optionally with an integer times count, Queue::assertPushed(JobClass::class, $times)) only — never pass a closure/callback argument (issue #756). The assertion's sole purpose is to verify the job was pushed onto the queue; a closure inspecting job properties belongs in a dedicated unit test of the job itself, not in the dispatch assertion. This is a CR finding under Queue assertion specificity (issue #756) in @rules/code-review/general.mdc Core Analysis Walk-through — raise it there once, never additionally under Strict rule compliance.

External Calls

  • Tests must not call external services.
  • Mock all HTTP requests.
  • Use local or in-memory database connections for tests.
  • Avoid DNS lookups in tests.

Consistency

  • Ensure new or modified tests follow existing project conventions.

Test Organization

  • Place every new test file under a directory path that mirrors the namespace of the production class it covers (e.g. App\Service\Billing\InvoiceCalculator → tests/Service/Billing/InvoiceCalculatorTest.php, Pekral\CursorRules\InstallerPath → tests/InstallerPathTest.php). A reviewer must be able to walk from a class to its tests by walking the parallel folder tree without searching.
  • One production class maps to one primary test file. The file name is {ClassName}Test.php (Pest functional test file using the same base name as the SUT plus the Test suffix). Helper / scenario test files extracted from a primary test file keep the same directory and a descriptive {ClassName}{Scenario}Test.php name so the relationship stays explicit.
  • Cross-cutting tests that do not target a single production class (full-stack feature flows, contract tests for an external API, install / boot scripts) go under an intent-named directory at the top of tests/ (tests/Feature/<flow>, tests/Contract/<vendor>, tests/Integration/<area>). Do not park them next to an unrelated class just to satisfy a parallel path.
  • Group multiple it() / test() blocks for the same SUT in the same test file when they share setup and subject. Do not split one SUT across many files unless the per-file size genuinely hurts readability (then use the {ClassName}{Scenario}Test.php form above).
  • Each it() / test() description states the scenario in plain language and matches what the body actually asserts. Examples of matching descriptions: it('returns zero for an empty cart'), test('throws InvalidArgumentException when the discount is negative'). Examples of non-matching descriptions that violate this rule: generic placeholders (it('it works'), test('test1'), test('happy path')), descriptions that name the method instead of the scenario (test('calculate'), it('handles getUser')), or descriptions that contradict the assertions (it('adds user') over a body that asserts removal). When changing what a test asserts, rename the description in the same change.

Comments in Tests

  • A test carries even less comment budget than production code. The it() / test() description already states the scenario, arrange-act-assert already states the shape, and the assertions already state the expectation — so a comment narrating the setup, labelling the three phases (// arrange), or paraphrasing the description is pure noise. Do not write it, and delete it when you find it in a test you are already changing, per @rules/php/core-standards.mdc Documentation.
  • Keep only what the test body cannot say: why a fixture holds an odd-looking value, the external quirk or upstream bug a regression pins, the issue / spec the case reproduces, or a genuinely non-obvious reason the assertion looks the way it does. Everything else belongs in the test description — when a comment is needed to tell the reader what the test does, rename the description instead of writing the comment.
  • A comment that survives is maintained. When you change what a test asserts, update or delete its comment in the same change — a comment describing an assertion the test no longer makes misleads the next reader exactly as a stale production docblock does.

Test Organization Review Hook

  • In code-review and pre-push contexts, every new or moved test file must satisfy the Test Organization rules above. Misplaced files, mismatched file names, and descriptions that do not match the asserted scenario are CR findings — see @skills/code-review/SKILL.md "Test organization" Core Analysis bullet for the severity matrix and Suggested Fix template.

Scope

  • Focus tests on behavior, not implementation details.

Execution

  • Run tests in parallel whenever possible (e.g. php artisan test --parallel or pest --parallel).

Quality

  • Prefer simple, readable tests over complex setups.
  • Use data providers (datasets) where they improve readability and reduce duplication across similar test cases.
  • Tests must not contain conditions (e.g., if, switch); split conditional logic into separate test cases or data providers instead.

Coverage

  • Every test change must be verified to be functional — run affected tests after each modification.
  • Require 100% code coverage for every changed or added code path — applies equally to code modifications and code review.
  • Before running coverage, discover the project's coverage command (prefer Phing target from build.xml/phing.xml; fall back to a Composer script in composer.json such as test:coverage or coverage). Do not assume a default command.
  • Coverage reporting is short by default (issue #528 follow-up). Run the coverage check on every change, but report the result on the published CR / tracker comment only when there is something the reader must act on:
    • uncovered changed lines — list every uncovered line as a Critical finding and render the ## Coverage section with the tool, exact command, and the uncovered-line list;
    • coverage tooling unavailable — raise the missing-tool case as a Critical finding and render the ## Coverage section with the reason in place of a result. When every changed line is at 100% coverage and the tool ran successfully, omit the ## Coverage section entirely, omit the Coverage: header line, and omit the coverage … slot from the final summary line. The CR is "clean" on the Counts line and the omission is the signal that coverage is satisfied — never emit 100% / clean / n/a placeholders for the section, the header line, or the summary slot. The coverage check itself still runs unconditionally on every CR; only the user-visible reporting is short-circuited.

Code Style and Quality Gates

  • Discover available fixers and checkers: prefer Phing targets (build.xml/phing.xml) over Composer scripts (composer.json).
  • Run available fixers on changed test files and fix any violations.
  • Run available checkers/analyzers on changed test files and resolve all reported errors by rewriting the flagged code, never by adding a suppression annotation (@rules/php/core-standards.mdc PHP Practices).
  • Complete all quality gates before finalizing test changes.

Test Review

  • After completing test changes, run a quick code review focused on test quality against these rules.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons