Golang: Unit Testing database/sql with go-sqlmock and testify/suite

Okky Muhamad Budiman
3 min readNov 15, 2020

--

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 dengan sqlMock
  • 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 !

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Okky Muhamad Budiman
Okky Muhamad Budiman

Written by Okky Muhamad Budiman

Tech Enthusiast, Punk Rock Software Engineer, Hustler Harder

Responses (1)

Write a response