Testing yang Melibatkan Database
Saat testing fitur yang berinteraksi dengan database, kita perlu memastikan database dalam kondisi bersih setiap test. Laravel menyediakan trait RefreshDatabase untuk ini.
RefreshDatabase Trait
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class UserTest extends TestCase
{
use RefreshDatabase; // Reset database setiap test
public function test_user_can_be_created(): void
{
$user = User::create([
"name" => "Budi",
"email" => "[email protected]",
"password" => bcrypt("password"),
]);
$this->assertDatabaseHas("users", [
"email" => "[email protected]",
]);
}
}
Model Factories
Factory adalah cara terbaik untuk membuat data test. Tidak perlu mengisi semua kolom secara manual:
// Membuat 1 user dan simpan ke database
$user = User::factory()->create();
// Membuat instance tanpa menyimpan ke database
$user = User::factory()->make();
// Membuat banyak user
$users = User::factory()->count(5)->create();
// Dengan state kustom
$admin = User::factory()->state([
"role" => "admin",
"is_active" => true,
])->create();
// Menggunakan named state dari factory
$admin = User::factory()->admin()->create();
Database Assertions
// Cek data ada di database
$this->assertDatabaseHas("users", [
"name" => "Budi",
"email" => "[email protected]",
]);
// Cek jumlah record
$this->assertDatabaseCount("users", 5);
// Cek data TIDAK ada di database
$this->assertDatabaseMissing("users", [
"email" => "[email protected]",
]);
// Cek data soft-deleted
$this->assertSoftDeleted("posts", [
"id" => $post->id,
]);
Testing Relationships
public function test_user_has_many_posts(): void
{
$user = User::factory()
->has(Post::factory()->count(3))
->create();
$this->assertCount(3, $user->posts);
$this->assertInstanceOf(Post::class, $user->posts->first());
}
public function test_post_belongs_to_user(): void
{
$post = Post::factory()
->for(User::factory())
->create();
$this->assertInstanceOf(User::class, $post->user);
$this->assertNotNull($post->user_id);
}
Testing Scopes dan Accessors
public function test_active_scope(): void
{
User::factory()->count(3)->create(["is_active" => true]);
User::factory()->count(2)->create(["is_active" => false]);
$activeUsers = User::active()->get();
$this->assertCount(3, $activeUsers);
}
public function test_full_name_accessor(): void
{
$user = User::factory()->create([
"first_name" => "Budi",
"last_name" => "Santoso",
]);
$this->assertEquals("Budi Santoso", $user->full_name);
}
Seeding dalam Test
public function test_with_seeded_data(): void
{
$this->seed(CategorySeeder::class);
$categories = Category::all();
$this->assertGreaterThan(0, $categories->count());
}
Test Isolation di Laravel
Test yang saling bocor datanya adalah mimpi buruk — test A lulus, lalu test B gagal karena urutan yang kebetulan. Laravel punya dua trait utama:
| Trait | Yang dilakukan | Kecepatan | Kapan pakai |
|---|---|---|---|
RefreshDatabase | Pertama: migrate:fresh (hapus semua tabel, migrate ulang). Selanjutnya: bungkus tiap test dalam transaksi lalu rollback. | Lambat di test pertama, cepat di test berikutnya | Default. Pakai saat schema berubah atau saat butuh jaminan DB bersih. |
DatabaseTransactions | Bungkus tiap test dalam transaksi lalu rollback. Tidak migrate ulang. | Paling cepat | Saat database sudah di-migrate manual dan tidak berubah. |
DatabaseMigrations | Jalankan migrate lalu migrate:rollback tiap test. | Sangat lambat | Jarang — hanya untuk test migration itu sendiri. |
Jebakan yang Sering Terjadi
- UUID conflict: kalau factory pakai UUID hardcoded atau sequencer tanpa reset, test paralel bisa tabrakan. Pakai
Str::uuid()di factory, jangan hardcode. - Seeder meninggalkan data:
RefreshDatabasetidak menjalankan seeder otomatis. Kalau perlu, panggil$this->seed(MySeeder::class)di setiap test, bukan disetUpyang berat. - Static cache / singleton: transaksi rollback DB, tapi tidak rollback Laravel cache atau variabel static. Bersihkan eksplisit dengan
Cache::flush(). - Queued job: job yang masih di antrian bisa bocor antar test. Pakai
Queue::fake(). - External API: HTTP call nyata adalah sumber flakiness. Pakai
Http::fake().
Parallel Testing
Laravel punya php artisan test --parallel yang menjalankan test di beberapa proses. Tiap proses butuh database terpisah (testing_1, testing_2, …). Pastikan:
- Tidak ada test yang pakai resource bersama tanpa isolasi (file system, port).
- Factory menghasilkan data unik per-test (gunakan
unique()di faker). - Tidak ada depend antar test — setiap test harus bisa jalan sendiri.