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 example
<?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 example
<?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.