Pendahuluan
Go Fiber dengan GORM menawarkan fondasi kuat untuk membangun API RESTful. Namun, saat menulis unit test, kita membutuhkan pendekatan yang lebih deterministik untuk repository yang berinteraksi langsung dengan database. Artikel ini mengulas pola testing repository GORM di Go Fiber, bermula dari setup mock DB/in-memory SQLite, menyiapkan fixture data, hingga menguji handler dan memastikan query, relasi, serta transaksi bekerja sesuai harapan.
Mengapa Harus Mock DB atau SQLite In-Memory
Direct hit ke database nyata dalam unit test mengundang flakiness karena ketergantungan state eksternal. Dua pendekatan populer:
- In-memory SQLite: Menjalankan GORM terhadap SQLite yang hanya hidup selama test. Sangat membantu untuk memastikan migrasi, relasi, dan constraint berjalan. Tidak membutuhkan mock tambahan, tapi lebih lambat dibanding mock.
- Mock DB (SQLMock atau interface GORM): Mengizinkan verifikasi query tanpa eksekusi. Lebih cepat dan memberikan kontrol ketat atas error path. Namun, perlu lebih banyak boilerplate karena harus menyimulasikan behavior database.
Pilih SQLite in-memory jika Anda ingin memvalidasi query SQL atau constraint berlapis, pilih mock DB bila fokus pada logika repository dan ingin menjalankan banyak kombinasi kasus error dengan cepat.
Menyiapkan Repository dan Interface
Menyaring dependency untuk repository membuat testing lebih gampang. Definisikan interface repository agar handler dan service hanya tahu kontrak, bukan implementasi GORM langsung.
type ProductRepository interface {
GetByID(ctx context.Context, id uint) (*models.Product, error)
Create(ctx context.Context, product *models.Product) error
Update(ctx context.Context, product *models.Product) error
}Implementasi GORM menyimpan *gorm.DB. Pastikan method menerima context.Context dan melakukan WithContext untuk mendukung tracing/test timeout.
Mock Database dengan SQLMock
SQLMock cocok untuk mem-verifikasi SQL yang dihasilkan GORM. Berikut langkah umum:
- Pasang
github.com/DATA-DOG/go-sqlmock. - Buat
*sql.DBmock lalu bungkus dengan GORM. - Buat expectation query dan hasilnya.
- Panggil repository dan verifikasi ekspektasi terpenuhi.
Contoh unit test sederhana untuk method GetByID:
func TestProductRepository_GetByID(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatal(err)
}
gormDB, _ := gorm.Open(mysql.New(mysql.Config{Conn: db}), &gorm.Config{})
repo := repository.NewProductRepository(gormDB)
rows := sqlmock.NewRows([]string{"id","name"}).AddRow(1, "Kopi Luwak")
mock.ExpectQuery("SELECT .* FROM `products` WHERE").WithArgs(1).WillReturnRows(rows)
prod, err := repo.GetByID(context.Background(), 1)
assert.NoError(t, err)
assert.Equal(t, "Kopi Luwak", prod.Name)
assert.NoError(t, mock.ExpectationsWereMet())
}Tips:
- Gunakan regex sederhana untuk expectation query agar tidak mudah gagal ketika GORM menambahkan alias.
- Warnakan transaksional behavior dengan
ExpectBegin,ExpectCommit, danExpectRollback. - Pastikan memanggil
ExpectationsWereMet()di akhir untuk memastikan semua expectation terpenuhi.
Mock DB paling berguna untuk memverifikasi bahwa repository mengonversi error SQL menjadi error domain yang sesuai.
Testing dengan SQLite In-Memory
SQLite in-memory cocok untuk memastikan relasi, constraint, dan transaksi bekerja. Langkah utamanya:
- Gunakan
sqlite.Open("file::memory:?cache=shared")agar GORM menggunakan database sementara. - Jalankan migrasi untuk schema yang relevan.
- Set up fixture data untuk relasi (misalnya product dan category).
- Cleanup setelah test dengan
db.Migrator().DropTable(...)jika perlu.
Contoh setup di test_main.go:
func newTestDB(t *testing.T) *gorm.DB {
db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
require.NoError(t, err)
err = db.AutoMigrate(&models.Product{}, &models.Category{})
require.NoError(t, err)
return db
}Fixture data bisa ditempatkan dalam helper:
func seedProductFixture(db *gorm.DB) *models.Product {
cat := models.Category{Name: "Minuman"}
prod := models.Product{Name: "Es Teh", Price: 8000, Category: &cat}
db.Create(&prod)
return &prod
}Dengan setup ini, kita dapat menguji relasi eager loading:
func TestProductRepository_LoadCategory(t *testing.T) {
db := newTestDB(t)
repo := repository.NewProductRepository(db)
prod := seedProductFixture(db)
got, err := repo.GetByIDWithCategory(context.Background(), prod.ID)
assert.NoError(t, err)
assert.Equal(t, "Minuman", got.Category.Name)
}Keuntungan SQLite in-memory adalah cocok untuk integrasi ringan. Trade-off: kecepatan lebih rendah dibanding mock dan lebih sulit mensimulasikan error spesifik kecuali menulis custom trigger.
Menulis Unit Test Handler di Go Fiber
Handler seharusnya bergantung pada interface repository. Ketika mengetes handler:
- Gunakan mock repository (misalnya dengan
testify/mock) untuk mengontrol respons. - Gunakan
httptest.NewRequestdanfiber.New().Testuntuk memanggil handler. - Periksa status code, header, dan payload JSON sesuai ekspektasi.
Contoh handler test untuk endpoint buat produk:
func TestCreateProductHandler(t *testing.T) {
app := fiber.New()
mockRepo := new(mocks.ProductRepository)
handler := handler.NewProductHandler(mockRepo)
mockRepo.On("Create", mock.Anything, mock.AnythingOfType("*models.Product")).Return(nil)
body := `{"name":"Kopi","price":15000}`
req := httptest.NewRequest(fiber.MethodPost, "/products", strings.NewReader(body))
req.Header.Set(fiber.HeaderContentType, fiber.MIMEApplicationJSON)
app.Post("/products", handler.Create)
resp, _ := app.Test(req)
assert.Equal(t, fiber.StatusCreated, resp.StatusCode)
mockRepo.AssertCalled(t, "Create", mock.Anything, mock.Anything)
}Catat strategi assertions:
- Query level: dengan SQLMock, pastikan SELECT/INSERT/UPDATE sesuai nama tabel dan argumen.
- Relasi: gunakan SQLite in-memory untuk cek eager load, gunakan assertion JSON untuk memastikan nested object muncul.
- Transaksi: mock
Begin/Commit/Rollbackdengan SQLMock untuk memverifikasi branch success/error.
Selain itu, jangan lupa menguji error path: misalnya, jika repository mengembalikan gorm.ErrRecordNotFound, handler harus menerjemahkan menjadi 404 dan tidak memanggil operasi tambahan.
Strategi Assertion dan Debugging
Beberapa tips praktis:
- Gunakan fixture builder pattern untuk membuat objek domain konsisten antar test.
- Log query dan errors pada setup test; GORM menyediakan logger dengan
Logger.LogMode(logger.Info)berguna saat debugging test yang gagal. - Pastikan isolation: setiap test memulai DB fresh. Untuk SQLite, gunakan transaction rollback atau drop table agar tidak saling mempengaruhi.
- Simulasikan race condition dengan menjalankan test paralel (gunakan
t.Parallel()) tapi hati-hati jika sharing DB.
Debugging failure SQLMock: tidak terpenuhinya expectation biasanya disebabkan GORM menambahkan clause alias. Tinjau query aktual di log GORM dan perbarui regex expectation.
Kesimpulan
Testing repository GORM di Go Fiber efektif bila memadukan mock DB dan SQLite in-memory sesuai kebutuhan. Mock DB cocok untuk verifikasi query & error path, sementara SQLite in-memory memberikan confidence terhadap relasi dan migrasi schema. Handler testing tetap bergantung pada interface repository dan menegakkan assertion status respons. Dengan fixture yang konsisten, testing menjadi lebih maintainable. Selalu bandingkan trade-off antara kecepatan test dan cakupan kebenaran data.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!