Introduction
Hi everyone, in this article, we will learn how to create unit test in Go, using mockery to testing a project's dependency. We often use mock testing as we unit test a function with another dependencies such as database, so we will learn on how to create it by using example that i'd wrote in here with the steps by steps on how to refactor, create unit test and using the mockery itself, let's get start into it!
Why Mockery?
Mockery provide us with the command to generate mock functions, that we can use for unit testing, so as a results we don't have to write our mock function from scratch, and it will be automatically provided by searching into our interface functions, inside our project's dependencies
To make this tutorial easier, we will prepare to refactor our code from previous article first, create a unit test, run the mockery and start using it in our unit test file in Go
Refactor
first thing to do, we're going to have a look into our old code, we going to define the interface in it, how do we do it? by creating a new root folder with name of repository
, from here this is what we call when we use our database dependency.
- config
|_ config.go
- models
|_ payment.go
- repository
|_ payment.go
- test.go
we will start by defining the interface in payment.go
inside our repository folder
type IPaymentRepository interface {
UpdatePayment(id string, payment models.Payment) (models.Payment, error)
DeletePayment(id string) (int64, error)
SelectPaymentWIthId(id string) (models.Payment, error)
CreatePayment(payment models.Payment) (int64, error)
}
since we are going to separate the database dependency from the function, we're going to create a struct that act to hold our database dependency value
type Repository struct {
Database *gorm.DB
}
then we move all the functions except the main one, inside our payment.go
, the complete repository will look like this
package repository
import (
"errors"
"github.com/yanoandri/simple-goorm/models"
"gorm.io/gorm"
)
type IPaymentRepository interface {
UpdatePayment(id string, payment models.Payment) (models.Payment, error)
DeletePayment(id string) (int64, error)
SelectPaymentWIthId(id string) (models.Payment, error)
CreatePayment(payment models.Payment) (int64, error)
}
type Repository struct {
Database *gorm.DB
}
func (repo Repository) UpdatePayment(id string, payment models.Payment) (models.Payment, error) {
var updatePayment models.Payment
result := repo.Database.Model(&updatePayment).Where("id = ?", id).Updates(payment)
if result.RowsAffected == 0 {
return models.Payment{}, errors.New("payment data not update")
}
return updatePayment, nil
}
func (repo Repository) DeletePayment(id string) (int64, error) {
var deletedPayment models.Payment
result := repo.Database.Where("id = ?", id).Delete(&deletedPayment)
if result.RowsAffected == 0 {
return 0, errors.New("payment data not update")
}
return result.RowsAffected, nil
}
func (repo Repository) SelectPaymentWIthId(id string) (models.Payment, error) {
var payment models.Payment
result := repo.Database.First(&payment, "id = ?", id)
if result.RowsAffected == 0 {
return models.Payment{}, errors.New("payment data not found")
}
return payment, nil
}
func (repo Repository) CreatePayment(payment models.Payment) (int64, error) {
result := repo.Database.Create(&payment)
if result.RowsAffected == 0 {
return 0, errors.New("payment not created")
}
return result.RowsAffected, nil
}
because we move all the functions into our repository folder, we will have to change the way to call our functions, we start to define which database value we want to hold in our struct inside our test.go
at the main
function
repo := repository.Repository{Database: db}
and from there we can use our functions, without putting any external dependency inside our parameters, for example
// create a payment
payment := models.Payment{
PaymentCode: "XXX-1",
Name: "Payment for item #1",
Status: "PENDING",
}
result, err := repo.CreatePayment(payment)
Create a Unit Test File
Luckily if we use VSCode, we can generate the unit test automatically by pressing ctrl + p
if you are in windows or command + p
and you will find the option to generate the functions like the screenshot below
and for the example we will create the test function of CreatePayment
by blocking the whole functions to generate it
package repository
import (
"testing"
"github.com/yanoandri/simple-goorm/models"
)
func TestRepository_CreatePayment(t *testing.T) {
type args struct {
payment models.Payment
}
tests := []struct {
name string
repo Repository
args args
want int64
wantErr bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.repo.CreatePayment(tt.args.payment)
if (err != nil) != tt.wantErr {
t.Errorf("Repository.CreatePayment() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("Repository.CreatePayment() = %v, want %v", got, tt.want)
}
})
}
}
Mockery
We will install mockery using our go command
go install github.com/vektra/mockery/v2@latest
after it finishes, we will create our mock functions by using mockery features to generate all of function mocks, by typing this command
mockery --all --keeptree
notice the mocks
folder of our project has been installed, and all of the functions has been mocked
and this is one example of our CreatePayment
mock functions
// CreatePayment provides a mock function with given fields: payment
func (_m *IPaymentRepository) CreatePayment(payment models.Payment) (int64, error) {
ret := _m.Called(payment)
var r0 int64
if rf, ok := ret.Get(0).(func(models.Payment) int64); ok {
r0 = rf(payment)
} else {
r0 = ret.Get(0).(int64)
}
var r1 error
if rf, ok := ret.Get(1).(func(models.Payment) error); ok {
r1 = rf(payment)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
Write our test functions to use mocks
Now let's include our mocked function to our unit test
package repository
import (
"errors"
"testing"
mocks "github.com/yanoandri/simple-goorm/mocks/repository"
"github.com/yanoandri/simple-goorm/models"
)
func TestRepository_CreatePayment(t *testing.T) {
type args struct {
payment models.Payment
}
tests := []struct {
name string
args args
want int64
wantErr bool
}{
// TODO: Add test cases.
{
name: "success_create_payment",
args: args{
models.Payment{
PaymentCode: "payment-code-011",
Status: "PENDING",
},
},
want: 1,
wantErr: false,
},
{
name: "failed_create_payment",
args: args{
models.Payment{},
},
want: 0,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
repo := &mocks.IPaymentRepository{}
if !tt.wantErr {
repo.On("CreatePayment", tt.args.payment).Return(tt.want, nil)
} else {
repo.On("CreatePayment", tt.args.payment).Return(tt.want, errors.New("Failed to create payment"))
}
got, err := repo.CreatePayment(tt.args.payment)
if (err != nil) != tt.wantErr {
t.Errorf("Repository.CreatePayment() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("Repository.CreatePayment() = %v, want %v", got, tt.want)
}
})
}
}
notice the repo is missing, it is because we didn't have to use the actual dependencies of database, and we want to mock it in every test session. And for now we will try to run our test by using command
go test ./... -v
Conclusions
This is just one function as an example to use mocks functions in unit test, you can continue to make test for UpdatePayment
, DeletePayment
and also SelectPaymentWIthId
. Mockery has provide us with easier way to generate our functions and as a results we can test our dependencies without creating the same functions twice from scratch, Hope this tutorial helps, and keep exploring! See ya!
Source:
Top comments (0)