About controller-testing
controller-testing is a Laravel skill for writing feature tests of HTTP controller actions. It helps developers derive tests from routes, authorization, validation, transport mode, and persistence behavior, with focused patterns for web and JSON APIs.
Key features
- 01Controller action test workflow
- 02Web and JSON API test patterns
- 03Nested route and binding checks
- 04Authorization and validation assertions
- 05Action-specific reference templates
Use cases
- 01Testing Laravel controller HTTP actions
- 02Checking nested route binding and access
- 03Verifying validation and persistence outcomes
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ hosmelq/ skills --skill controller-testingAfter installing, type/controller-testingin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
references/route-patterns.mdreferences/actions/create.mddestroy.mdedit.mdindex.mdshow.mdstore.mdupdate.mdreferences/modes/api-json.mdreferences/validation/api-login-validation.mdprepare-for-validation.mdrequired-with-and-array.mdscoped-exists-and-unique.mdstore-validates-fields.mdupdate-validates-fields.mdDetails
From the project READMEController Testing
When to Apply
Activate this skill only when the task is about Laravel controller feature tests (action-level HTTP behavior for a controller) and includes signals like:
- The test file path is under
tests/Feature/Http/Controllers/**(includingtests/Feature/Http/Controllers/Api/**). This is the primary trigger. - The task is explicitly about a controller action and uses action-level
describe('create' | 'destroy' | 'edit' | 'index' | 'show' | 'store' | 'update'). - Controller route assertions using
route(...), nested parameters, and route-model binding scope checks. - Auth/authorization outcomes and controller transport assertions:
- web/session (
get,post,patch,delete,assertInertia, redirects), or - API/JSON (
getJson,postJson,patchJson,deleteJson, JSON assertions).
- web/session (
Do not activate this skill based on describe('store') alone (many non-controller feature tests use the same verbs, for example Fortify/auth flows).
Do not use this skill for model/unit tests, service-layer tests, console commands, or non-HTTP behavior.
Kill Switch / Precedence
For controller-focused feature tests, this skill takes precedence over pest-testing.
If pest-testing was selected for controller test work, stop and switch to controller-testing immediately.
Quick Start
# Create file only when needed
php artisan make:test --pest Http/Controllers/<Name>ControllerTest --no-interaction
# Run only the affected file
php artisan test --compact tests/Feature/Http/Controllers/<Name>ControllerTest.php
# Optional focused rerun
php artisan test --compact --filter="<test name>"
Decision Workflow
- Inspect the controller action, form request, policy, resource, and route definition to derive the contract under test.
- Choose transport mode: web/session or API/JSON.
- Determine route shape (flat vs nested) and parameter keys.
- For each action under test, apply this baseline in order (as applicable):
- requires authentication
- policy denial (
403) - binding mismatch (
404) - validation datasets (
store/update) - success response + persistence assertions
- Load only the reference files needed for the action/rule pattern.
Output Guardrails (Keep Controller Tests Clean)
These guardrails exist to prevent noisy, inconsistent tests.
- Treat the controller contract as the source of truth. Templates are starting points only; adapt them to the actual request payload, response shape, redirects, resources, and policy flow implemented by the controller.
- Keep
403tests minimal. For forbidden cases, you usually do not need to send a full payload (authorization should fail before validation). - Validation datasets must be small and stable:
- Each dataset
datashould only include the field(s) needed to trigger that rule. Do not repeat a full "valid payload" for every case. - Assert validation messages for every failing field. Do not use keys-only expectations in these datasets.
- When multiple fields share a
sometimes|required-style blank-value rule, prefer one dataset case that submits blank strings for all of those fields and asserts the required message for each of them. Do not reduce that pattern to a single representative field. - Prefer
post(...)/patch(...)directly; avoidfrom(...)unless you cannot get a reliable redirect-back assertion otherwise.
- Each dataset
- Prefer route-based redirects: use
assertRedirectToRoute(...)when a named route exists; useassertRedirect(...)only when the destination is not a named route or is config-driven.
Canonical 403 vs 404 Rule
Use this as the source of truth:
- If route bindings resolve correctly and authorization denies access, assert
assertForbidden()(403). - If route-model binding fails (wrong parent/child chain, scoped binding mismatch), assert
assertNotFound()(404).
Actor context to keep outcomes deterministic:
403tests: authenticate an in-scope actor that can resolve bindings but lacks permission.- Validation and success-path tests: authenticate an authorized in-scope actor.
404binding-mismatch tests: any authenticated actor is acceptable because binding fails first.
Reference Map
Load only what you need:
- API/JSON adaptation guide and full examples:
references/modes/api-json.md
- Nested route naming and parameter composition:
references/route-patterns.md
- Per-action templates (web/session-first):
references/actions/create.mdreferences/actions/destroy.mdreferences/actions/edit.mdreferences/actions/index.mdreferences/actions/show.mdreferences/actions/store.mdreferences/actions/update.md
- Validation catalogs (merge only required rules):
references/validation/store-validates-fields.mdreferences/validation/update-validates-fields.md
- Focused validation patterns (prefer first when matching rules exist):
references/validation/required-with-and-array.mdreferences/validation/scoped-exists-and-unique.mdreferences/validation/prepare-for-validation.mdreferences/validation/api-login-validation.md
Action references are web/session-first templates. For API/JSON controllers, keep the same action structure and adapt assertions with references/modes/api-json.md based on the controller/resource contract under test.
Baseline Assertions by Transport
Web/session mode:
requires authentication-> redirect to login route.- Validation failures ->
assertRedirectBackWithErrors(...). - Page actions ->
assertOk()+assertInertia(...). - Mutation actions -> redirect + persistence assertions.
- Toast/flash assertions are optional and app-specific.
API/JSON mode:
- Protected endpoints:
requires authentication->assertUnauthorized(). - Public endpoints: skip auth-required test unless the endpoint is protected.
- Validation failures ->
assertUnprocessable()+assertJsonValidationErrors(...). - Success actions ->
assertOk()/assertCreated()/assertNoContent()+ JSON + persistence assertions. - Assert validation messages, not just keys, so the test verifies the exact API contract.
Notes
- One-level, two-level, and three-level examples are patterns, not limits.
- For
destroy, choose persistence assertions by model behavior:assertSoftDeleted(...)for soft-deleting models.assertModelMissing(...)otherwise.
- Use the app's identifier shape in assertions (
id,public_id, slug, route key).