From acc
Provides PHP 8.4 testing knowledge: pyramid, AAA pattern, naming conventions, isolation principles, DDD guidelines, PHPUnit/Pest patterns.
How this skill is triggered — by the user, by Claude, or both
Slash command
/acc:testing-knowledgeThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Quick reference for PHP testing patterns, principles, and best practices.
Quick reference for PHP testing patterns, principles, and best practices.
/\
/ \ Functional (10%)
/────\ - E2E, browser tests
/ \ - Slow, fragile
/────────\ Integration (20%)
/ \ - DB, HTTP, queues
/────────────\Unit (70%)
/ \- Fast, isolated
/________________\- Business logic
Rule: 70% unit, 20% integration, 10% functional. Invert the pyramid = slow, brittle test suite.
public function test_order_calculates_total_with_discount(): void
{
// Arrange — set up test data
$order = new Order(OrderId::generate());
$order->addItem(new Product('Book', Money::EUR(100)));
$discount = new PercentageDiscount(10);
// Act — execute the behavior
$total = $order->calculateTotal($discount);
// Assert — verify the outcome
self::assertEquals(Money::EUR(90), $total);
}
Rules:
test_{method}_{scenario}_{expected}
| Example | Method | Scenario | Expected |
|---|---|---|---|
test_calculate_total_with_discount_returns_reduced_amount | calculateTotal | with discount | returns reduced amount |
test_confirm_when_already_shipped_throws_exception | confirm | when already shipped | throws exception |
test_email_with_invalid_format_fails_validation | Email (VO) | with invalid format | fails validation |
it('calculates total with discount applied')
it('throws exception when confirming shipped order')
it('fails validation for invalid email format')
| Rule | Check |
|---|---|
| One test = one behavior | Single assertion group |
| Test is documentation | Name reads as specification |
| No logic in tests | No if/for/while |
| Fast execution | <100ms per unit test |
| Mock interfaces only | Never mock VO, Entity, final |
| ≤3 mocks per test | More = design smell |
| Behavior over implementation | Test WHAT, not HOW |
| Component | Test Focus | Mocks Allowed |
|---|---|---|
| Value Object | Validation, equality, immutability | None |
| Entity | State transitions, business rules | None |
| Aggregate | Invariants, consistency, events | None |
| Domain Service | Business logic spanning aggregates | Repository (Fake) |
| Application Service | Orchestration, transactions | Repository, EventDispatcher |
| Repository | CRUD operations | Database (SQLite) |
<?php
declare(strict_types=1);
namespace Tests\Unit\Domain;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\TestCase;
#[Group('unit')]
#[CoversClass(Email::class)]
final class EmailTest extends TestCase
{
public function test_creates_valid_email(): void
{
$email = new Email('[email protected]');
self::assertSame('[email protected]', $email->value);
}
public function test_throws_for_invalid_format(): void
{
$this->expectException(InvalidArgumentException::class);
new Email('invalid');
}
}
<?php
declare(strict_types=1);
namespace Tests\Integration\Infrastructure;
use PHPUnit\Framework\Attributes\Group;
use Tests\DatabaseTestCase;
#[Group('integration')]
final class DoctrineOrderRepositoryTest extends DatabaseTestCase
{
private OrderRepositoryInterface $repository;
protected function setUp(): void
{
parent::setUp();
$this->repository = $this->getContainer()->get(OrderRepositoryInterface::class);
}
public function test_saves_and_retrieves_order(): void
{
// Arrange
$order = OrderMother::pending();
// Act
$this->repository->save($order);
$found = $this->repository->findById($order->id());
// Assert
self::assertNotNull($found);
self::assertTrue($order->id()->equals($found->id()));
}
}
| Type | Purpose | When to Use |
|---|---|---|
| Stub | Returns canned answers | External API responses |
| Mock | Verifies interactions | Event publishing |
| Fake | Working implementation | InMemory repository |
| Spy | Records calls | Logging, notifications |
Need to verify a call was made?
├── Yes → Mock or Spy
└── No → Need real behavior?
├── Yes → Fake
└── No → Stub
| Smell | Detection | Fix |
|---|---|---|
| Logic in Test | if, for, while in test | Extract to helper or parameterize |
| Mock Overuse | >3 mocks | Refactor design, use Fakes |
| Mystery Guest | External files, hidden data | Inline test data or use Builder |
| Eager Test | Tests multiple behaviors | Split into separate tests |
| Fragile Test | Breaks on refactor | Test behavior, not implementation |
| Aspect | Unit Test | Integration Test | Contract Test |
|---|---|---|---|
| Speed | Fast | Slow | Medium |
| Scope | Single class | Service + deps | API boundary |
| Isolation | Full | Partial | Consumer/Provider |
| Use case | Business logic | DB, queues | Service-to-service |
When to use: Microservices REST APIs, message-based systems, event schema verification.
| Pattern | Duration | Load Profile | Goal |
|---|---|---|---|
| Smoke | 1-2 min | Minimal | Verify script works |
| Load | 10-30 min | Expected traffic | Performance baseline |
| Stress | 10-30 min | 1.5-2x expected | Find breaking point |
| Spike | 5-10 min | Sudden burst | Test auto-scaling |
| Soak | 2-8 hours | Sustained | Find memory leaks |
| Failure | How to Inject | What It Tests |
|---|---|---|
| Network latency | Sleep in middleware | Timeout handling |
| Service error | Return 500 randomly | Circuit breaker |
| Connection refused | Close port | Fallback behavior |
| Slow database | Query delay | Query timeout handling |
| Strategy | How | Trade-off |
|---|---|---|
| Test containers | Docker Compose per test | Isolated but slow |
| Shared staging | Dedicated environment | Fast but interference |
| Data seeding | API/DB setup per test | Controlled but complex |
| Snapshot restore | DB snapshot before tests | Fast reset |
For detailed information, load these reference files:
references/unit-testing.md — Unit test patterns and examplesreferences/integration-testing.md — Integration test setup and patternsreferences/ddd-testing.md — Testing DDD components (VO, Entity, Aggregate, Service)references/advanced-testing.md — Contract testing (Pact), chaos testing, load testing patterns (ramp-up, spike, soak), E2E distributed testingnpx claudepluginhub dykyi-roman/awesome-claude-code --plugin accProvides cross-language testing patterns including test pyramid, unit/integration/E2E tests, AAA structure, test doubles, naming conventions, and isolation for databases/external services.
Provides stack-agnostic testing strategies, the testing pyramid, test design heuristics, and patterns for fixing flaky tests and applying TDD.
PHPUnit and Pest patterns for any PHP project: test structure, naming, data providers, test doubles (mocks/stubs/fakes), fixtures, and coverage targets. Stack-agnostic — referenced by every PHP plugin in the marketplace. Use this skill to: - Structure unit and integration tests with PHPUnit or Pest consistently. - Drive cases with data providers / datasets instead of copy-pasted test bodies. - Use test doubles with discipline — mock collaborators, not the system under test. - Set up and tear down fixtures cleanly and aim for a meaningful coverage target. Do NOT use this skill for: - PHP language idioms — see php-foundation:php-conventions. - Composer / autoloading — see php-foundation:composer-tooling. - Framework-specific test helpers (Laravel RefreshDatabase/actingAs, Symfony WebTestCase/KernelTestCase) — those are injected by the framework plugin's QA phase.