Golang: Unit Testing database/sql with go-sqlmock and testify/suite
Dalam dunia Software Engineer kita mengenal dengan namanya unit testing yang merupakan metode software testing di dalam ilmu computer programming, semacam automated testing, dia akan bekerja mengecek setiap code kita apakah sudah sesuai dengan yang diinginkan, baik dari segi inputan maupun behavior method kita.
Jika pada artikel sebelumnya kita membahas pembuatan unit test + mocking technique dalam level handler dan service pada aplikasi golang kita, sekarang saya akan melanjutkan untuk menjelaskan unit test pada repositories atau dalam level database.
note: bagi yang belum membaca artikel tentang Unit Test with Mocking Technique bisa dilihat disini.
Oke pertama untuk membuat unit test pada level database saya menggunakan go-sqlmock
dan suit
sebagai setting setup.
Pada kasus ini saya memiliki table product di dalam database saya yang berisi
- ID: Int
- Name: Varchar
- Price: Decimal
- Description: Text
dan saya define model product sebagai blueprint dari tabel product :
package models
type Product struct {
ID int `db:"id"`
Name string `db:"name"`
Price float32 `db:"price"`
Description string `db:"description"`
}
Disini saya ingin membuat repository untuk product tersebut. Memakai database/sql
sebagai package sql
:
package repository
import (
"context"
"database/sql"
"log"
"unit-test-repository/models"
)
type Repository interface {
GetAllProduct() ([]models.Product, error)
GetProductByID(id int) (*models.Product, error)
}
type productRepo struct {
db *sql.DB
}
func (p *productRepo) GetAllProduct() ([]models.Product, error) {
products := []models.Product{}
rows, err := p.
db.
QueryContext(context.Background(),
"SELECT id, name, price, description FROM products",
)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var product models.Product
if err := rows.Scan(
&product.ID,
&product.Name,
&product.Price,
&product.Description,
); err != nil {
// Check for a scan error.
// Query rows will be closed with defer.
log.Fatal(err)
}
products = append(products, product)
}
return products, nil
}
func (p *productRepo) GetProductByID(id int) (*models.Product, error) {
product := models.Product{}
err := p.
db.
QueryRowContext(context.Background(),
"SELECT id, name, price, description FROM products WHERE id=?", id,
).
Scan(&product.ID,
&product.Name,
&product.Price,
&product.Description,
)
if err != nil {
log.Fatalf("query error: %v\n", err)
}
return &product, nil
}
func NewProductRepository(db *sql.DB) Repository {
return &productRepo{
db: db,
}
}
Saya hanya membuat 2 method yaitu :
- `GetAllProduct` untuk mengambil list data product.
- `GetProductByID` untuk mengambil data product berdasarkan id.
Testing
Di part ini saya membuat product_repository_test
sebagai file test repository diatas.
type Suite struct {
suite.Suite
mock sqlmock.Sqlmock
repository Repository
}
Ada beberapa tahapan dalam membuat unit test untuk repository ini, diantaranya:
- Setup
suite
- Run
suite
- Test semua method
- Assert dan check return sesuai ekspektasi
Setup Suite
kita akan mocking sql.DB
menggunakan sqlmock.New()
dan memasukan kedalam NewProductRepository(db)
.
func (s *Suite) SetupSuite() {
var (
db *sql.DB
err error
)
db, s.mock, err = sqlmock.New()
require.NoError(s.T(), err)
s.repository = NewProductRepository(db)
}
Test Get All Product
Kita mencoba untuk membuat unit test untuk GetAllProduct.
Langkah-langkah yaitu :
- Define variable
ExpectQuery
dengansqlMock
- Call method
GetAllProduct
- Assert dan bandingan hasilnya dengan ekspektasi data yang kita bikin
func (s *Suite) TestProductRepo_GetProduct() {
var (
id = 1
name = "test-name"
price = 10000
description = "lorem ipsum"
)
s.mock.ExpectQuery(regexp.QuoteMeta(
`SELECT id, name, price, description FROM products`)).
WillReturnRows(sqlmock.NewRows([]string{"id", "name", "price", "description"}).
AddRow(id, name, price, description))
res, err := s.repository.GetAllProduct()
exp := []models.Product{
models.Product{
ID: 1,
Name: "test-name",
Price: 10000,
Description: "lorem ipsum",
},
}
require.NoError(s.T(), err)
require.Equal(s.T(), exp, res)
}
Test Get All Product By Id
Langkah-langkah nya hampir sama dengan yang diatas namun ditambah arguments id.
func (s *Suite) TestProductRepo_GetProductByID() {
var (
id = 1
name = "test-name"
price = 10000
description = "lorem ipsum"
)
s.mock.ExpectQuery(regexp.QuoteMeta(
`SELECT id, name, price, description FROM products`)).
WithArgs(1).
WillReturnRows(sqlmock.NewRows([]string{"id", "name", "price", "description"}).
AddRow(id, name, price, description))
res, err := s.repository.GetProductByID(1)
exp := models.Product{
ID: 1,
Name: "test-name",
Price: 10000,
Description: "lorem ipsum",
}
require.NoError(s.T(), err)
require.Equal(s.T(), &exp, res)
}
Result


Tidak sulit bukan ? Selamat mencoba !