---
title: Use RefreshDatabase + Factories in Tests — No Manual Inserts
impact: MEDIUM
impactDescription: manual DB inserts create brittle tests tightly coupled to schema; factories stay in sync with model changes
tags: testing, factories, refresh-database, pest, phpunit, laravel
---

## Use RefreshDatabase + Factories in Tests

Hardcoded `DB::table()->insert()` or `User::create([...])` with raw data creates brittle tests. Use model factories with `RefreshDatabase` for isolated, maintainable tests.

**Wrong:**

```php
class OrderTest extends TestCase
{
    protected function setUp(): void
    {
        parent::setUp();
        DB::table('users')->insert(['id' => 1, 'name' => 'Test', 'email' => 'test@test.com', 'password' => 'xxx']);
        DB::table('products')->insert(['id' => 1, 'name' => 'Widget', 'price' => 100, 'stock' => 10]);
        // Test fails when schema changes
    }

    public function test_can_place_order()
    {
        $response = $this->post('/orders', ['product_id' => 1, 'qty' => 2]);
        $this->assertEquals(200, $response->getStatusCode());
    }
}
```

**Correct (PHPUnit):**

```php
class OrderTest extends TestCase
{
    use RefreshDatabase; // wraps each test in a transaction, rolled back after

    public function test_authenticated_user_can_place_order(): void
    {
        $user    = User::factory()->create();
        $product = Product::factory()->create(['price' => 100, 'stock' => 10]);

        $response = $this->actingAs($user)
            ->postJson('/api/orders', ['product_id' => $product->id, 'qty' => 2])
            ->assertCreated()
            ->assertJsonPath('data.total', 200);

        $this->assertDatabaseHas('orders', ['user_id' => $user->id, 'total' => 200]);
        $this->assertDatabaseHas('products', ['id' => $product->id, 'stock' => 8]);
    }

    public function test_order_fails_when_insufficient_stock(): void
    {
        $user    = User::factory()->create();
        $product = Product::factory()->create(['stock' => 1]);

        $this->actingAs($user)
            ->postJson('/api/orders', ['product_id' => $product->id, 'qty' => 5])
            ->assertUnprocessable()
            ->assertJsonValidationErrors(['qty']);
    }
}
```

**Factory patterns:**
```php
// Specific state
$admin  = User::factory()->admin()->create();
$banned = User::factory()->banned()->unverified()->create();

// With relations
$post = Post::factory()->for($user)->hasComments(3)->create();
```
