Create Backend Tests
Execution-phase PHPUnit implementation. Planning-phase: ns-spec-driven/references/unit-test-task-generator.md.
Session boot
Load .nextstage-harness/rules/backend-tests-rules.mdc and backend-rules.mdc when present. See references/backend-testing-architecture.md for layout, run contract, and bootstrap summary.
Phase 0 — Resolve or bootstrap backend test root
Before any test class code, locate the PHPUnit project or create minimal structure.
0.1 — Detect existing tests (brownfield)
Search the repo in this order:
backend/phpunit.xmlphpunit.xmlat repo roottests/Unit/ortests/Feature/under the directory that containscomposer.jsonwith PHPUnit
Record the directory containing phpunit.xml as {backend_root}. All paths below are relative to {backend_root}.
0.2 — Bootstrap when nothing exists (greenfield)
If backend application code exists (app/, src/, or equivalent) but no phpunit.xml and no tests/:
| Rule | Detail |
|---|---|
| Location | backend/ when that package exists; else repo root |
| Tree | Per references/backend-testing-architecture.md — phpunit.xml, tests/TestCase.php, tests/Unit/, tests/Feature/ |
| Dependencies | PHPUnit via existing composer.json — never add to frontend or unrelated packages |
Then set {backend_root} to that directory.
Forbidden on greenfield: assuming tests exist, running PHPUnit on the host, or scaffolding tests outside the backend package.
0.3 — Post-bootstrap documentation
When docs/context/stack-confirmed.md or architecture-rules.md exists, add or update the backend test row: {backend_root}, docker test container, and the PHPUnit run command from Phase 5.
Do not proceed to Phase 1 until {backend_root} is confirmed and phpunit.xml is readable.
Phase 1 — Discovery (before feature code)
- Code under test — read implementation files (Service, FormRequest, Controller, etc.)
- Existing tests — read matching files under
tests/Unit/andtests/Feature/; extend, do not duplicate - Project patterns — factories,
RefreshDatabase, response envelope, multitenancy helpers from harness rules - Task contract — when implementing a unit-test planning task, follow its scenario checklist
Phase 2 — Write tests
- Unit: mock dependencies — no real DB/queue/cache I/O
- Feature: HTTP tests with auth, 401/403, cross-tenant P0 when applicable
- Naming:
{Component}Test.php, descriptive snake_case method names - Match module paths:
tests/Unit/Modules/{Domain}/,tests/Feature/Modules/{Domain}/
Phase 3 — Run (mandatory contract)
See also ../../ns-harness/references/docker-and-testing.md.
- Run
docker ps— confirm the documented test container (ask once if undocumented) - Execute inside the test container at
{backend_root}workdir:
timeout 120 docker exec -w {workdir} {test_container} \
vendor/bin/phpunit --testdox --stop-on-failure --stop-on-error {optional_filter}
- Timeout: if the command is killed at 120s (e.g. exit 124), treat the run as dead/hung — abort, report blocker, do not silently retry
- Never run
vendor/bin/phpunitorphpunit.shon the host
Filtered runs (single class or method) use the same flags and timeout.
Phase 4 — Report
- Pass: list files added/changed
- Fail: first failing test name + assertion from
--testdoxoutput - Hung/timeout: state 120s limit exceeded; invoke
ns-investigatorif root cause unclear
References
| File | When |
|---|---|
references/backend-testing-architecture.md |
Layout, bootstrap, run contract |
../ns-spec-driven/references/unit-test-task-generator.md |
Task contract source |
Related skills
ns-spec-drivenreferences/unit-test-task-generator.md— planning tasksns-investigator— failing or hung test debuggingns-coder— ad-hoc fixes outside planned unit-test tasks