Claude
Skills
Sign in
Back

phpunit-integration-test-generation

Included with Lifetime
$97 forever

Use this skill when the user asks to generate, write, or create integration tests for a Shopware 6 source class whose contract requires wired-up code — phrases like "generate integration tests for X", "write an integration test for this controller", "test this indexer", "create an integration test for the message handler". Detects supported integration patterns (controller/route, scheduled-task, message-handler, indexer, DAL-persistence flow, multi-service coordinator) and applies a template producing an INTEGRATION-001..008-compliant test using IntegrationTestBehaviour against the real DAL, container, and HTTP/messaging. When the source class is unit-shape (no persistence, no kernel state, no wiring under test), returns SKIPPED and points at phpunit-unit-test-writing. Do NOT activate for unit tests or migration tests (use phpunit-migration-test-generation).

Productivity

What this skill does


# PHPUnit Integration Test Generation

Generate Shopware-compliant PHPUnit integration tests that exercise wired-up code through `IntegrationTestBehaviour`, produce integration-shape assertions, and pass PHPStan and PHPUnit validation.

## File Write Restrictions

Write ONLY to:
- `tests/integration/**` — Integration test files

NEVER write to:
- `src/**` — Source code (read-only)
- `tests/unit/**` — Out of scope (use `phpunit-unit-test-generation`)
- `tests/migration/**` — Out of scope (use `phpunit-migration-test-generation`)
- Any other directory

## Workflow

```dot
digraph integration_test_generation {
    "User invocation" [shape=doublecircle];
    "Validate input" [shape=box];
    "Source is PHP class in src/?" [shape=diamond];
    "Analyze source" [shape=box];
    "Integration pattern detected?" [shape=diamond];
    "Select template + apply" [shape=box];
    "Write test file" [shape=box];
    "Validate (PHPStan, PHPUnit, ECS)" [shape=box];
    "All green?" [shape=diamond];
    "Fix iteration (max 3)" [shape=box];
    "Iterations exhausted?" [shape=diamond];
    "Report SUCCESS" [shape=doublecircle];
    "Report PARTIAL" [shape=doublecircle];
    "Report SKIPPED (defer to unit gen)" [shape=doublecircle];
    "Report FAILED" [shape=doublecircle];

    "User invocation" -> "Validate input";
    "Validate input" -> "Source is PHP class in src/?";
    "Source is PHP class in src/?" -> "Report FAILED" [label="no"];
    "Source is PHP class in src/?" -> "Analyze source" [label="yes"];
    "Analyze source" -> "Integration pattern detected?";
    "Integration pattern detected?" -> "Report SKIPPED (defer to unit gen)" [label="no — unit-shape SUT"];
    "Integration pattern detected?" -> "Select template + apply" [label="yes"];
    "Select template + apply" -> "Write test file";
    "Write test file" -> "Validate (PHPStan, PHPUnit, ECS)";
    "Validate (PHPStan, PHPUnit, ECS)" -> "All green?";
    "All green?" -> "Report SUCCESS" [label="yes"];
    "All green?" -> "Iterations exhausted?" [label="no"];
    "Iterations exhausted?" -> "Report PARTIAL" [label="yes"];
    "Iterations exhausted?" -> "Fix iteration (max 3)" [label="no"];
    "Fix iteration (max 3)" -> "Validate (PHPStan, PHPUnit, ECS)";
}
```

## Phase 1: Validate Input

1. Verify single file provided
2. Verify file exists and is a PHP class (not interface/trait/abstract)
3. Verify path starts with `src/`

If validation fails, return FAILED with reason.

## Phase 2: Analyze Source

Read the source class and detect which integration pattern applies. See references/source-analysis.md for the full detection logic.

### Step 1: Extract Metadata

- Class name, full namespace
- `#[Package('...')]` attribute value (default to `'framework'` if absent)
- Constructor dependencies (FQCN list)
- Area from namespace (`Core`, `Administration`, `Storefront`, `Elasticsearch`)
- Public methods and their return types

### Step 2: Detect Pattern

Walk the decision table in references/source-analysis.md. Patterns are evaluated top to bottom; the first match wins.

| Pattern | Indicator |
|---------|-----------|
| `controller` | Extends `AbstractController` or `AbstractRoute` / `Abstract*Route`, or methods carry `#[Route]` |
| `scheduled-task` | Extends `ScheduledTaskHandler`, OR has `#[AsMessageHandler]` and the `__invoke()` parameter is a `ScheduledTask` subclass |
| `message-handler` | Has `#[AsMessageHandler]` on `__invoke()` AND the parameter is a domain message (not a `ScheduledTask`) |
| `indexer` | Extends `EntityIndexer` (Shopware) |
| `dal-flow` | Constructor takes `EntityRepository` AND public methods write through it (`create`, `update`, `upsert`) AND the SUT contract is "the data was persisted" or "the indexer/event was triggered" |
| `multi-service` | Constructor takes ≥ 3 non-boundary dependencies (boundary set defined in `INTEGRATION-002`), and at least 2 are stateful (DAL, indexer, event dispatcher, system config) |

### Step 3: Decide

- **Pattern detected** → Continue to Phase 3
- **No pattern detected** → The SUT is unit-shape. Return SKIPPED with `skip_type: unit_test_more_appropriate` and reason: "Source class fits a unit-shape pattern (no persistence, no wiring, no multi-service coordination under test). Use `phpunit-unit-test-generation` instead." Reference the relevant refactoring pattern in `phpunit-integration-to-unit-migrating/references/refactoring-patterns.md` when the SUT looks like a factory, compiler pass, single subscriber, parser, constraint-only validator, or DAL materializer.

## Phase 3: Generate Test

### Step 1: Determine Test Path

Mirror source path with `src/` → `tests/integration/`:
- `src/Core/Content/Product/ProductIndexer.php` → `tests/integration/Core/Content/Product/ProductIndexerTest.php`
- `src/Core/Checkout/Cart/SalesChannel/CartLoadRoute.php` → `tests/integration/Core/Checkout/Cart/SalesChannel/CartLoadRouteTest.php`

Namespace mirrors the path: `Shopware\Tests\Integration\Core\Content\Product`.

### Step 2: Apply Template

Use the integration test template at templates/integration-test.md. The template has a base block plus one conditional section per pattern. Include exactly one pattern section based on Phase 2 detection. The base block defers all behavior trait `use` statements to the conditional section, because the trait choice varies by pattern:

- **Always (base block)**: namespace, `#[CoversClass]`, `#[Package]`, `@internal`, empty class shell
- **`controller`**: `IntegrationTestBehaviour + SalesChannelApiTestBehaviour` (or admin/storefront equivalent), `IdsCollection`, `KernelBrowser` built via `createCustomSalesChannelBrowser([...])`, `#[Group('store-api')]`, request invocation, response assertions
- **`scheduled-task`**: `DatabaseTransactionBehaviour + KernelTestBehaviour` (lighter than `IntegrationTestBehaviour`), `parent::setUp()`, direct `$handler->run()` invocation, raw SQL `Connection::fetchOne(...)` assertions
- **`message-handler`**: `IntegrationTestBehaviour`, direct `($this->handler)($message)` invocation, DAL read-back assertion (bus dispatch only when transport routing is part of the SUT contract)
- **`indexer`**: `DatabaseTransactionBehaviour + KernelTestBehaviour`, `parent::setUp()`, realtime flow — write via DAL captures `$event`, `$indexer->update($event)` returns `?EntityIndexingMessage`, assert on the returned message's data
- **`dal-flow`**: `IntegrationTestBehaviour`, arrange prerequisite state via DAL, invoke SUT, assert persisted result via separate DAL read
- **`multi-service`**: `IntegrationTestBehaviour` composed with domain behaviours per dependency (`AppSystemTestBehaviour`, `GuzzleTestClientBehaviour`, `MailTemplateTestBehaviour`), configure inputs through `SystemConfigService` / DAL, invoke SUT, assert effects across multiple collaborators

Fill placeholders using Phase 2 metadata. Use `IdsCollection` (`Shopware\Core\Test\Stub\Framework\IdsCollection`) for any test that manages more than one entity ID, and type all repository properties with the generic PHPDoc `@var EntityRepository<XxxCollection>`. Leave `TODO:` markers where the behavior-specific arrange or assertion is genuinely SUT-dependent and not derivable from class structure — these are the spots a human or follow-up step must complete.

### Step 3: Rule Compliance Checklist

The generated test must satisfy all `INTEGRATION-001..008` rules at write time. Self-check before validation:

- INTEGRATION-001: `use IntegrationTestBehaviour;` present
- INTEGRATION-002: SUT and primary collaborators retrieved from container (not mocked); only boundary types may be mocked (HTTP client, mailer, clock, randomness)
- INTEGRATION-003: any DDL, filesystem write, or cache write has a matching teardown (or is wrapped in try/finally)
- INTEGRATION-004: assertions don't depend on wall-clock time or unsourced randomness — assertions on UUIDs go through referential lookups, not literal-value equality
- INTEGRATION-005: no `#[Depends]` between test methods
- INTEGRATION-006: never `m

Related in Productivity