| name | phpunit-unit-test-generation |
| version | 4.2.2 |
| description | Internal sub-skill. Do not auto-activate. Use only when explicitly invoked by name by another skill or agent. |
| user-invocable | false |
| context | fork |
| agent | test-writing:test-generator |
| allowed-tools | Read, Grep, Glob, Write, Edit, mcp__plugin_dev-tooling_php-tooling |
PHPUnit Test Generation
Generate Shopware-compliant PHPUnit unit tests that pass PHPStan and PHPUnit validation.
File Write Restrictions
Write ONLY to:
tests/unit/** - Unit test files
NEVER write to:
src/** - Source code (read-only)
tests/integration/** - Out of scope
- Any other directory
Quick Start
- Read the target source class
- Check if test is required (see Phase 1)
- Determine test category (A-E)
- Apply the matching template
- Validate with PHPStan and PHPUnit
- Fix any errors and repeat
- Generate completion report
Phase 1: Analyze Source Class
Step 1: Check Coverage Exclusions
Before analyzing the source class, check if the project's phpunit.xml.dist (or phpunit.xml) excludes it from coverage. Files excluded from coverage do not need unit tests.
- Read
phpunit.xml.dist from the project root
- Find
<exclude> rules inside the <coverage> or <source> section
- Match the source file path against each rule:
<directory suffix="X">path</directory> — excluded if file is under path AND filename ends with X
<file>path/to/File.php</file> — excluded if relative path matches exactly
- If excluded → Return SKIPPED with
skip_type: coverage_excluded and reason: "Source file excluded from coverage by phpunit.xml.dist (<matched-rule>)"
If phpunit.xml.dist is not found, skip this step.
Step 2: Determine If Test Is Required
Before generating any test, evaluate if the class/method requires one.
Quick check: Does the method body contain ONLY return <literal|constant|property|passthrough-new|delegation>?
- Yes -> NO TEST NEEDED — Return SKIPPED with
skip_type: no_logic and reason describing the pattern (e.g., "Pure accessor - no logic to test")
- No (has conditionals/loops/transformations) -> Continue to Step 3
For detailed rules on what to test vs skip, see references/test-requirement-rules.md.
Step 3: Analyze Source Structure
Read the target class to determine:
- Public methods - What behaviors to test
- Constructor dependencies - What to mock/stub
- Return types - Expected outcomes
- Exception scenarios - Error paths (see references/exception-patterns.md)
- Deprecation markers -
@deprecated tags, Feature::triggerDeprecationOrThrow(), Feature::silent(), Feature::callSilentIfInactive() (see references/deprecation-guards.md)
Step 4: Detect Category
Use the decision tree to select the appropriate category:
Has constructor dependencies?
├── No → Is it an Exception class?
│ ├── Yes → Category E
│ └── No → Category A (DTO)
└── Yes → Uses EntityRepository?
├── Yes → Category D (DAL)
└── No → Implements EventSubscriberInterface or FlowAction?
├── Yes → Category C (Flow/Event)
└── No → Category B (Service)
For detailed category criteria, see references/category-detection.md.
Phase 2: Essential Rules
Apply these mandatory conventions when generating tests.
Quick Reference
| Rule | Requirement |
|---|
| File location | tests/unit/ mirroring src/ path |
| Class attribute | #[CoversClass(TargetClass::class)] required |
| Assertions | Use static:: not $this-> |
| Base class | Extend PHPUnit\Framework\TestCase |
| Method naming | test + Action + Condition + ExpectedResult |
| Attribute order | PHPDoc -> DataProvider -> TestDox -> method |
| One behavior | NO conditionals in tests |
TestDox Phrasing
TestDox MUST be a predicate phrase starting with an action verb:
- Good: "creates product", "returns null", "throws exception"
- Bad: "It creates...", "Should return...", "Tests that..."
Mocking Priority
- Real implementation - Use actual objects when simple
- Shopware stubs -
StaticEntityRepository, StaticSystemConfigService, Generator
- PHPUnit mocks - Only for external/IO dependencies
For createStub vs createMock selection, see references/mocking-patterns.md.
For complete rules, see references/essential-rules.md.
Phase 3: Generate Test
Step 1: Select Template
Based on category from Phase 1:
| Category | Template |
|---|
| A (DTO) | templates/category-a-dto.md |
| B (Service) | templates/category-b-service.md |
| C (Flow/Event) | templates/category-c-flow.md |
| D (DAL) | templates/category-d-dal.md |
| E (Exception) | templates/category-e-exception.md |
For data provider and decoration contract patterns, see references/common-patterns.md.
Step 2: Replace Placeholders
{Module} - Core module (e.g., Content, Checkout, System)
{Submodule} - Submodule path (e.g., Product, Cart\LineItem)
{TargetClass} - Class name being tested
{Entity} - Entity name for DAL tests
{Method} - Method name being tested
{Expected} - Expected outcome description
{Condition} - Condition description
{Exception} - Exception class name
Step 3: Write Test File
Write to correct location: tests/unit/{path matching src}/{ClassName}Test.php
Phase 4: Validate and Fix
Validation Loop
- [ ] PHPStan passes (0 errors)
- [ ] PHPUnit passes (all tests green)
- [ ] ECS passes (code style)
Step 1: Run PHPStan
{
"paths": ["tests/unit/Path/To/GeneratedTest.php"],
"error_format": "json"
}
Zero errors = pass.
Step 2: Fix PHPStan Errors
Apply fixes for common errors. See references/validation-error-mapping.md.
Step 3: Run PHPUnit
{
"paths": ["tests/unit/Path/To/GeneratedTest.php"],
"output_format": "result-only"
}
All tests passing = success. If tests fail, re-run without output_format to get failure details for Step 4.
Step 4: Fix Test Failures
Apply fixes for common failures. See references/validation-error-mapping.md.
Step 5: Run ECS Check and Fix
Check for violations, then apply fixes if needed.
Repeat Until Pass
Loop through Steps 1-5 until all validations pass.
Maximum iterations: Stop after 3 failed attempts and proceed to Phase 5.
Phase 5: Generate Report
For output format and examples, see references/output-format.md.
Status Determination
| Condition | Status | skip_type |
|---|
| All validations pass | SUCCESS | — |
| Test generated, validation issues remain after 3 iterations | PARTIAL | — |
| File excluded from coverage in phpunit.xml.dist | SKIPPED | coverage_excluded |
| No testable logic (per Test Requirement Rules) | SKIPPED | no_logic |
| Invalid input (not a PHP class, file not found) | FAILED | — |
Report Contents
- Summary: Source path, test path, status, category
- Generation Details: Test method count, template used
- Validation Results: PHPStan/PHPUnit/ECS pass/fail counts
- Remaining Issues (if PARTIAL): Location, error, status table
Additional Resources
Reference Files
For detailed patterns and techniques, consult:
- references/test-requirement-rules.md - Decision tree for what to test
- references/category-detection.md - How to categorize source classes
- references/essential-rules.md - Naming, attribute, structure rules
- references/validation-error-mapping.md - Error codes and fixes
- references/shopware-stubs.md - StaticEntityRepository, Generator patterns
- references/exception-patterns.md - expectExceptionObject, expectException + message, exception codes
- references/mocking-patterns.md - createStub vs createMock, intersection types, configuration, side-effect verification
- references/deprecation-guards.md - DisabledFeatures, skipTestIfActive/InActive, Feature::silent, class-level guards
- references/common-patterns.md - Data providers, AAA structure, event subscribers, decoration pattern
- references/output-format.md - Report output contract
Templates
Category-specific test generation templates in templates/:
- category-a-dto.md - Simple DTO/Entity tests
- category-b-service.md - Service tests with dependencies
- category-c-flow.md - Flow/Event subscriber tests
- category-d-dal.md - DAL/Repository tests
- category-e-exception.md - Exception handling tests