Testing Knowledge Base
Quick reference for PHP testing patterns, principles, and best practices.
Testing Pyramid
/\
/ \ 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.
AAA Pattern (Arrange-Act-Assert)
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:
- One blank line between sections
- Single Act per test
- Assert behavior, not implementation
Naming Conventions
PHPUnit Style
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 |
Pest Style
it('calculates total with discount applied')
it('throws exception when confirming shipped order')
it('fails validation for invalid email format')
Test Isolation Principles
DO
DON'T
Quick Quality Checklist
| 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 |
DDD Component Testing
| 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 8.4 Test Patterns
Unit Test Template
<?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');
}
}
Integration Test Template
<?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()));
}
}
Test Doubles Quick Reference
| 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 |
Decision Matrix
Need to verify a call was made?
├── Yes → Mock or Spy
└── No → Need real behavior?
├── Yes → Fake
└── No → Stub
Common Test Smells
| 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 |
Advanced Testing Patterns
Contract Testing (Pact)
| 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.
Load Testing Patterns
| 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 |
Chaos Testing (Failure Injection)
| 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 |
E2E Distributed Testing
| 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 |
References
For detailed information, load these reference files:
references/unit-testing.md — Unit test patterns and examples
references/integration-testing.md — Integration test setup and patterns
references/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
Converted and distributed by TomeVault — claim your Tome and manage your conversions.