Golang Unit Testing with Gorm and Sqlmock PostgreSQL — Simplest setup

Golang Unit Testing with Gorm and Sqlmock PostgreSQL — Simplest setup

Golang Jul 14, 2023

In this tutorial I will highlight the Gorm and mock SQL part which I believe is the hard part.

There will be no unit test assert here to make it short and concise.

Simplest Gorm INSERT and test case

Here is the simple product file

package product

import (
 "gorm.io/gorm"
)

type Product struct {
 Code  string
 Price uint
}

func createProduct(db *gorm.DB) {
 db.Create(&Product{Code: "D42", Price: 100})
}

Then we have the test file

package product

import (
 "testing"

 "github.com/DATA-DOG/go-sqlmock"
 "gorm.io/driver/postgres"
 "gorm.io/gorm"
)

func TestCreateProduct(t *testing.T) {
 mockDb, mock, _ := sqlmock.New()
 dialector := postgres.New(postgres.Config{
  Conn:       mockDb,
  DriverName: "postgres",
 })
 db, _ := gorm.Open(dialector, &gorm.Config{})

 // CASE 1
 createProduct(db)

 // CASE 2
 // fmt.Println(mock)
 rows := sqlmock.NewRows([]string{"Code", "Price"}).AddRow("D43", 100)
 mock.ExpectQuery(`SELECT`).WillReturnRows(rows)
 createProduct(db)
}

Then we run the test

go test -cover

Got the pass result.

The key here to mock the database is sqlmock.New() this will represent the mock database and interact with Gorm.

You can extend the result to assert create success by sqlmock.ExpectExec with return value (1, 1) to asserted.

More on Querying

In more realistic case, we might need to check something before create like check the existing Code , now the mock object become more useful

package product

import (
 "gorm.io/gorm"
)

type Product struct {
 Code  string
 Price uint
}

func createProduct(db *gorm.DB) {
 var product Product
 err := db.First(&product, "code = ?", "D42").Error
 if err != nil {
  return
 }
 db.Create(&Product{Code: "D42", Price: 100})
}


Then our test file will add the mock.ExpectQuery

package product

import (
 "testing"

 "github.com/DATA-DOG/go-sqlmock"
 "gorm.io/driver/postgres"
 "gorm.io/gorm"
)

func TestCreateProduct(t *testing.T) {
 mockDb, mock, _ := sqlmock.New()
 dialector := postgres.New(postgres.Config{
  Conn:       mockDb,
  DriverName: "postgres",
 })
 db, _ := gorm.Open(dialector, &gorm.Config{})

 // CASE 1
 createProduct(db)

 // CASE 2
 // fmt.Println(mock)
 rows := sqlmock.NewRows([]string{"Code", "Price"}).AddRow("D43", 100)
 mock.ExpectQuery(`SELECT`).WillReturnRows(rows)
 createProduct(db)
}

Note the this is RegExp so I just use the SELECT statement

Also note that there is branching:

  1. If function found code = ? then it return
  2. Function run through the end and create the record

So I add two case to make the coverage become 100%

go test -cover
PASS
        product coverage: 100.0% of statements
ok      product 0.004s

Common mistake

testing: warning: no tests to run

You need the TestNameFunction to begin with capital letter.

Hope this help and see you next time

Tags

TeamCMD

We are CODEMONDAY team and provide a variety of content about Business , technology, and Programming. Let's enjoy it with us.