Integration Testing
Integration tests verify interactions between components: repositories with databases, buses with handlers, middleware chains.
Scope
- Repository implementations with real databases
- Command/Query buses with handlers
- Middleware behavior
- Event dispatching and listening
Test Structure
test('SessionMiddleware starts session', function (): void {
$session = new FakeSession();
expect($session->isStarted())->toBeFalse();
$middleware = new SessionMiddleware($session);
$request = new ServerRequest('GET', '/');
$handler = new FakeRequestHandler(new Response(200));
$middleware->process($request, $handler);
expect($session->isStarted())->toBeTrue();
});
Repository Integration Tests
test('SqliteBookRepository persists and retrieves book', function (): void {
$pdo = new PDO('sqlite::memory:');
$pdo->exec(file_get_contents('database/schema.sqlite.sql'));
$repository = new SqliteBookRepository($pdo);
$book = Book::create($id, $title, $author, $isbn);
$repository->save($book);
$retrieved = $repository->find($book->id());
expect($retrieved)->not->toBeNull()
->and($retrieved->id())->toBe($book->id());
});
Test Database Setup
beforeEach(function (): void {
$this->pdo = new PDO('sqlite::memory:');
$this->pdo->exec(file_get_contents('database/schema.sqlite.sql'));
});
Multi-Database Repository Testing
Repository tests run against all database implementations (InMemory, SQLite, MySQL, PostgreSQL) using shared test templates.
Base Test Template Pattern
Define tests once, register for each implementation:
// BaseBookRepositoryTests.php
function registerBaseBookRepositoryTests(): void
{
test('saves and retrieves a book', function (): void {
$book = Book::create($id, $title, $author, $isbn);
$this->repository->save($book);
$found = $this->repository->getById($id);
expect($found)->not->toBeNull()
->and($found->getId())->toBe($id);
});
test('findAll returns all books', function (): void {
// Test implementation...
});
// Additional shared tests...
}
Implementation-Specific Test Files
Each repository implementation includes the base tests and configures its database:
// SqliteBookRepositoryTest.php
require_once __DIR__.'/BaseBookRepositoryTests.php';
registerBaseBookRepositoryTests();
beforeAll(function () use (&$dbName): void {
$dbName = PdoRegistry::initializeSqlite(true);
});
beforeEach(function (): void {
PdoRegistry::getConnection()->beginTransaction();
$driver = new SqliteDriver(PdoRegistry::getConnection());
$this->repository = new SqliteBookRepository($driver, new TimestampIdGenerator());
});
afterEach(function (): void {
PdoRegistry::getConnection()->rollBack();
});
PdoRegistry
Centralized database connection management for tests:
// Initialize database with production schema
PdoRegistry::initializeSqlite(schemaProduction: true);
PdoRegistry::initializeMysql(schemaProduction: true);
PdoRegistry::initializePostgresql(schemaProduction: true);
// Get shared connection
$pdo = PdoRegistry::getConnection();
// Cleanup after tests
PdoRegistry::cleanupMysql($dbName);
PdoRegistry::cleanupPostgresql($dbName);
MySQL and PostgreSQL create unique database names per test run to allow parallel execution.
Test Isolation with Transactions
Each test runs in a transaction that rolls back after completion:
beforeEach(function (): void {
PdoRegistry::getConnection()->beginTransaction();
});
afterEach(function (): void {
PdoRegistry::getConnection()->rollBack();
});
This ensures tests don't affect each other while avoiding slow database recreation.
Using Fake Objects
The tests/*/Fake/ directories contain test doubles for sessions, handlers, and other services. See Test Doubles for details.
Running Tests
Best Practices
- Isolate each test (fresh database state)
- Prefer InMemory implementations for speed
- Don't mock what you're testing
- Keep tests focused on component boundaries
- Use transactions for cleanup when possible
- Share test logic via base test functions
See Also
- Repository Implementations - What integration tests verify
- Database Schemas - Test database setup
- Event Bus - Event dispatching tests
- Middleware Stack - Middleware behavior tests