testing php applications
Test Fixtures
A fixture is the known state a test needs before it runs. Good fixtures are small, readable, and owned by the test. They make the important setup visible without dragging unrelated application data into every scenario.
Create Only The State You Need
A repository test for published products may need one published and one draft row. Loading a large production-like dump makes failures slower and harder to reason about.
Reset State Reliably
Each test should be independent. Use setup hooks, factories, transactions, truncation, or isolated databases according to the boundary. Avoid test order dependencies.
<?php
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
final class ProductRepositoryTest extends TestCase
{
private PDO $pdo;
protected function setUp(): void
{
$this->pdo = new PDO('sqlite::memory:');
$this->pdo->exec(
'CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, status TEXT)'
);
}
public function testRepositoryReturnsPublishedProductsOnly(): void
{
$this->pdo->exec(
"INSERT INTO products (name, status)
VALUES ('Desk lamp', 'published'), ('Draft chair', 'draft')"
);
$names = $this->pdo
->query("SELECT name FROM products WHERE status = 'published'")
->fetchAll(PDO::FETCH_COLUMN);
$this->assertSame(['Desk lamp'], $names);
}
}
This example recreates a tiny schema for each test. Larger suites may use migrations, factories, and transaction rollback while preserving the same independence.
Common Mistakes
- Sharing mutable fixtures across tests.
- Loading large opaque fixture files for small rules.
- Depending on test execution order.
- Using unrealistic fixtures that bypass important database constraints.
What To Practise
- Create minimal fixtures.
- Reset state between tests.
- Use realistic constraints at integration boundaries.
Practice
Practice: Create Product Fixtures
Define fixture rows for a repository test that returns published products only.
Requirements
- Create one published and one draft product.
- Keep fields minimal but realistic.
- Explain how state resets between tests.
- Assert that only the published row is returned.
Show solution
The fixture contains the smallest state that proves filtering.
<?php
declare(strict_types=1);
$fixtures = [
['id' => 42, 'name' => 'Desk lamp', 'status' => 'published'],
['id' => 51, 'name' => 'Draft chair', 'status' => 'draft'],
];
$published = array_values(array_filter(
$fixtures,
static fn (array $product): bool => $product['status'] === 'published'
));
echo count($published) . PHP_EOL;
echo $published[0]['name'] . PHP_EOL;
// Prints:
// 1
// Desk lamp
A database integration test would insert equivalent rows and reset the database state after the test.