From laravel-api-tool-kit
Follows step-by-step workflow to write tests for features: pick type, name by concern, happy paths with DB assertions, failures, and edge cases.
npx claudepluginhub ahmedesa/laravel-api-tool-kitlaravel-api-tool-kit/workflows/# Workflow: Write Tests Use when adding tests for any feature. See `rules/testing.md` for principles — this is the step-by-step. --- ## Step 1 — Pick the Test Type Use the decision table in `rules/testing.md#pick-the-test-type`. When in doubt: feature test. --- ## Step 2 — Name the File by Concern Not `CarTest.php`. Ask: what behavior does this file cover? | Concern | File name | |---|---| | CRUD operations | `CarCrudTest.php` | | Permissions / visibility | `CarAudienceTest.php` | | Input validation | `CreateCarValidationTest.php` | | Notifications / events | `CarNotificationsTest.p...
/write-testsWrites unit and integration tests by detecting test framework, analyzing code, planning strategy, and implementing AAA-pattern tests with mocks, edge cases, errors, security, and performance coverage.
/write-testsWrites unit and integration tests by detecting test framework, analyzing code, planning strategy, and implementing AAA-pattern tests with mocks, edge cases, errors, security, and performance coverage.
/write-testsWrites unit tests for source files using Given-When-Then conventions, fixture extraction, and edge case coverage. Detects Vitest, Jest, or pytest frameworks.
/write-testsWrite tests for existing code: analyze the code, identify cases to test, and generate complete test file
Use when adding tests for any feature. See rules/testing.md for principles — this is the step-by-step.
Use the decision table in rules/testing.md#pick-the-test-type. When in doubt: feature test.
Not CarTest.php. Ask: what behavior does this file cover?
| Concern | File name |
|---|---|
| CRUD operations | CarCrudTest.php |
| Permissions / visibility | CarAudienceTest.php |
| Input validation | CreateCarValidationTest.php |
| Notifications / events | CarNotificationsTest.php |
Multiple concerns → multiple files from the start. Never dump everything into one file.
Extend BaseTestCase (create it if missing — see rules/testing.md#basetestcase-pattern).
If writing a unit test (external service, invisible side effect, or pure logic) → see rules/testing.md#writing-unit-tests for the three patterns.
If testing a job → create an executeJobDirectly() helper first (see rules/testing.md#testing-jobs).
If the feature uses Redis or cache state → create infrastructure setup helpers alongside factories (see rules/testing.md#infrastructure-state-redis-cache).
If the feature emits mail, notifications, or queued jobs → assert the content via closures, not just that something was sent (see rules/testing.md#asserting-side-effects).
Arrange → Act → Assert. For writes: assert status AND DB. For reads: assert status AND JSON shape.
test('user can create a car', function () {
$user = $this->loginAsUser();
$this->postJson('/api/cars', ['name' => 'Tesla', 'color' => 'red'])
->assertCreated();
$this->assertDatabaseHas('cars', [
'name' => 'Tesla',
'user_id' => $user->id,
]);
});
Every feature needs at least one failure test. Cover whichever apply:
test('unauthenticated user cannot create a car', function () {
$this->postJson('/api/cars', ['name' => 'Tesla'])->assertUnauthorized();
});
Apply these only when the feature calls for them — do NOT add ceremonial tests for cases that don't exist.
| If the feature has... | Add a test for... | Pattern |
|---|---|---|
| Different behavior per role / relationship | Multi-actor in one test | rules/testing.md#multi-actor-testing |
| Behavior that changes when state changes mid-scenario | State transition | rules/testing.md#state-transitions |
| A threshold, limit, or configurable count | Boundary values (at, above, zero) | rules/testing.md#boundary-values |
| Optional configuration fields | Null/missing config fallback | rules/testing.md#boundary-values |
| Date, duration, or expiry logic | Time-frozen test with travelTo() | explicit absolute timestamps |
| Recurring/periodic logic (daily reset, cron, timezone) | Safe instant — pin away from boundary | rules/testing.md#safe-instants-for-periodic-logic |
assertXxx() with a failure messageuses()Stop there. Do not over-engineer the test helpers.
travelTo() for any date logicassertJsonPath used instead of assertSee