Test Fixtures & setUp() Best Practices
debt(d7/e5/b5/t7)
Closest to 'only careful code review or runtime testing' (d7). The detection_hints indicate automated detection is 'no' even though phpunit and pest are listed as tools. The problematic patterns — setUp() with many state mutations, tests passing only due to setUp() side effects, shared DB state causing order-dependent failures — are not caught by static analysis; they manifest as intermittent or order-dependent test failures that require careful review or deliberate randomised test ordering to surface.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix describes a conceptual shift (object creation vs. state setup in setUp()), but common_mistakes reveal multiple intertwined issues: order-dependent tests, static property mutations, over-populated fixtures, and missing transaction rollbacks. Correcting these across a test suite means touching many test files and potentially restructuring test data factories or database seeding strategies — more than a single-line swap but not a full architectural rework.
Closest to 'persistent productivity tax' (b5). The applies_to scope covers web, cli, and queue-worker contexts, meaning test fixture misuse can affect any context where tests are written. Shared mutable fixtures slow down debugging across many work streams — every failing test investigation must account for possible state leakage — but the burden is contained to the test layer rather than shaping the entire production system's architecture.
Closest to 'serious trap' (t7). The misconception explicitly states that developers believe sharing fixture state speeds up the test suite, which is a plausible performance-motivated reasoning. This contradicts the principle that tests should be independent, and the failure mode (intermittent, order-dependent failures) is particularly insidious because it looks like flakiness rather than a design mistake. This is a well-documented gotcha that contradicts reasonable intuitions about resource sharing.
Also Known As
TL;DR
Explanation
PHPUnit's setUp() runs before each test method — use it for common object creation but avoid coupling tests through shared mutable state. Prefer object mother classes (UserMother::typical(), UserMother::admin()) or factory functions that return fully configured objects with sensible defaults — tests override only the properties relevant to that specific assertion. This follows the Arrange-Act-Assert pattern cleanly. For database tests, use transactions rolled back in tearDown() (DatabaseTransactions trait in Laravel) rather than re-seeding — order-of-magnitude faster. Avoid static state in fixtures. Keep setUpBeforeClass() (runs once per class) for truly expensive shared resources (database schema creation, container boot) while keeping instance-level setUp() lightweight.
Common Misconception
Why It Matters
Common Mistakes
- Tests that depend on execution order — each test must set up and tear down its own state.
- Fixtures shared via static properties — mutations persist across tests.
- Over-populated fixtures — tests that need one user should not load 50 rows of seed data.
- Not using database transactions with rollback for database tests — each test gets a clean database state.
Code Examples
// Shared static fixture — order-dependent tests:
class UserTest extends TestCase {
private static User $user;
public static function setUpBeforeClass(): void {
self::$user = User::create(['email' => 'alice@example.com']);
}
public function testDeactivate(): void {
self::$user->deactivate(); // Mutates shared fixture
}
public function testEmail(): void {
// Fails if testDeactivate ran first — user is now deactivated
}
}
// Object Mother pattern
class UserMother {
public static function typical(): User {
return new User(id: 1, email: 'user@test.com', role: Role::User);
}
public static function admin(): User {
return new User(id: 2, email: 'admin@test.com', role: Role::Admin);
}
}