Test yang andal butuh data uji yang andal juga: user dengan email unik, post dengan relasi valid, skenario admin maupun tamu — semuanya dalam hitungan baris. Model factory adalah alat baku untuk itu, dan di dalam test ia punya trik tambahan: state untuk skenario, relasi beruntun untuk data berkeluarga, sequence untuk keberagaman, serta seeder untuk data dasar yang dipakai banyak test. Dasar factory sudah kita bahas di chapter Model Factories ; fokus kali ini adalah pemakaiannya di dalam test case.

create vs make di Dalam Test #

User::factory()->create() mengembalikan model yang sudah tersimpan ke database, sedangkan make() hanya membangun instance di memori. Test yang tidak butuh baris database sebaiknya memakai make() — lebih cepat dan tidak meninggalkan jejak:

php
public function test_make_membuat_instance_tanpa_menyentuh_database(): void
{
    $post = Post::factory()->make([
        'title' => 'Post Bayangan',
        'body' => 'Hanya di memori, tidak pernah disimpan.',
    ]);

    $this->assertSame('Post Bayangan', $post->title);
    $this->assertFalse($post->exists);
    $this->assertDatabaseCount('posts', 3);
}

Property $post->exists bernilai false dan jumlah baris posts tidak berubah — bukti make() tidak pernah membuka transaksi INSERT. Aturan praktisnya: pakai create() ketika test akan meng-query atau me-render data itu, dan make() ketika cukup nilai atributnya.

State untuk Skenario Berbeda #

Skenario aplikasi jarang hanya punya satu wajah user. State factory mengubah beberapa atribut default tanpa mendefinisikan factory baru — cukup satu method per skenario di dalam factory:

php
// di database/factories/UserFactory.php
public function unverified(): static
{
    return $this->state(fn (array $attributes) => [
        'email_verified_at' => null,
    ]);
}

public function admin(): static
{
    return $this->state(fn (array $attributes) => [
        'is_admin' => true,
    ]);
}

Di dalam test, state dirangkai setelah factory() dan sebelum create() — perbandingan admin terverifikasi melawan tamu yang belum verifikasi email menjadi dua baris:

php
public function test_state_admin_dan_unverified(): void
{
    $admin = User::factory()->admin()->create([
        'email' => '[email protected]',
    ]);
    $tamu = User::factory()->unverified()->create([
        'email' => '[email protected]',
    ]);

    $this->assertTrue($admin->is_admin);
    $this->assertNotNull($admin->email_verified_at);

    $this->assertFalse($tamu->is_admin);
    $this->assertNull($tamu->email_verified_at);
}

State di factory, bukan di test, membuat skenario itu bisa dipakai ulang di seluruh suite. Pattern sama berlaku untuk data non-user — misalnya state draft() pada PostFactory yang menganulir published_at menjadi null.

Data Berelasi dalam Satu Baris #

Fixture sering kali berbentuk keluarga data: seorang user beserta post-post-nya. Method has() merangkai factory relasi dalam satu ekspresi:

php
public function test_relasi_user_memiliki_tiga_post(): void
{
    $siti = User::factory()
        ->has(Post::factory()->count(3))
        ->create(['email' => '[email protected]']);

    $this->assertCount(3, $siti->fresh()->posts);
    $this->assertDatabaseCount('posts', 6);
}

Satu baris tersebut membuat user dan tiga post yang seluruh user_id-nya mengarah ke user itu — relasi hasMany teruji tanpa satu pun id yang ditulis manual. Kebalikannya, for(), memilih induk untuk child: Post::factory()->count(3)->for($user)->create(). Ingat pula definition() di PostFactory sudah berisi 'user_id' => User::factory(), sehingga setiap post yang dibuat sendirian otomatis punya penulisnya.

sequence untuk Data Beragam #

Tiga post dengan judul sama persis tidak menguji apa pun tentang pengurutan. sequence() memberi atribut berbeda untuk tiap model yang dibuat:

php
$posts = Post::factory()->count(3)->sequence(
    ['title' => 'Post Pertama'],
    ['title' => 'Post Kedua'],
    ['title' => 'Post Ketiga'],
)->create([
    'user_id' => User::query()->firstWhere('email', '[email protected]')->id,
]);

$this->assertSame(
    ['Post Pertama', 'Post Kedua', 'Post Ketiga'],
    $posts->pluck('title')->all(),
);

Array pertama berlaku untuk model pertama, array kedua untuk model kedua, dan seterusnya — dikombinasikan dengan override di create() untuk atribut yang bernilai sama. Method create() sendiri mengembalikan collection sehingga urutannya bisa langsung di-assert.

Seeder: per-Test atau di setUp #

Ketika sekelompok test membutuhkan data dasar yang sama, sebaiknya seeder dijalankan sekali di setUp() daripada diulang di setiap method — DRY untuk fixture:

php
protected function setUp(): void
{
    parent::setUp();

    $this->seed(PostSeeder::class);
}

public function test_data_seeder_tersedia_di_setiap_test(): void
{
    $this->assertDatabaseCount('posts', 3);
    $this->assertDatabaseHas('posts', ['title' => 'Refactor dengan Tenang']);
}

Setiap test dalam class ini mulai dengan satu user Budi dan tiga post dari PostSeeder — hasil transaksi RefreshDatabase yang di-rollback lalu seeder dijalankan lagi oleh setUp(). Sebaliknya, jika hanya satu dua test yang butuh data khusus, panggil $this->seed() di dalam method test itu saja agar test lain tetap ramping dan cepat.

Jalankan seluruh suite-nya:

bash
php artisan test tests/Feature/FactoryScenarioTest.php

Output yang Dihasilkan:

text
  PASS  Tests\Feature\FactoryScenarioTest
  ✓  data seeder tersedia di setiap test                                 0.15s
  ✓  state admin dan unverified                                          0.01s
  ✓  make membuat instance tanpa menyentuh database                      0.01s
  ✓  relasi user memiliki tiga post                                      0.01s
  ✓  sequence menghasilkan data beragam                                  0.01s
  ✓  state draft menonaktifkan published at                              0.01s

  Tests:    6 passed (14 assertions)
  Duration: 0.28s

Dengan factory, seeder, dan isolasi database di tangan, fondasi testing kita lengkap. Selanjutnya kita mengarahkan persenjataan ini ke wilayah yang lebih spesifik: pengujian authentication, authorization, dan API — tempat skenario login, permission, dan token menambah dimensi baru pada data uji. Rujukan lengkap ada di dokumentasi Eloquent Factories serta Database Seeders .