Help us improve
Share bugs, ideas, or general feedback.
From acc
Provides PHP 8.4 testing knowledge: pyramid, AAA pattern, naming conventions, isolation principles, DDD guidelines, PHPUnit/Pest patterns.
npx claudepluginhub dykyi-roman/awesome-claude-code --plugin accHow 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.
Provides 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.
Use when writing tests, designing test strategy, or reviewing test coverage. Covers test pyramid, naming, mocking, and flaky test policy.
Share bugs, ideas, or general feedback.
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('user@example.com');
self::assertSame('user@example.com', $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 testing