Golang : Unit Test with Mocking Technique

Okky Muhamad Budiman
5 min readSep 26, 2020

--

https://www.seguetech.com/wp-content/uploads/2014/10/segue-blog-benefits-unit-testing.png

Unit Testing 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.

Mengapa Unit Testing ?

Unit Testing sangat penting sekali karena bisa mempersingkat waktu seorang pengembang software(programmer) dalam pembuatan sebuah aplikasi. Serta dengan unit testing kita dapat meminimalisir error terjadi sebelum masuk dalam fase QA ataupun rilis, sehingga pada akhirnya akan menghemat waktu dan budget.

Unit Testing dalam Golang

Bahasa pemrograman golang mendukung sekali dengan unit testing, dengan membuat file xxx_test.go file tersebut akan terdeteksi oleh golang sebagai file testing ketika menjalankan perintah go test.

Adapun contoh membuat unit testing dalam golang sebagai berikut.

package main

import (
"fmt"
"unit-testing/hello"
)

func main() {
greetings := hello.Hello("Jhon Doe")
fmt.Println(greetings)
}

Buatlah file bernama hello.go di dalam folder hello.

package hello


func Hello(name string) string {
if name == "" {
return "name should not be empty"
}
return name
}

Dalam package main diatas terdapat pemanggilan function hello yang akan mengembalikan nilai dari sebuah parameter string.

Sekarang kita akan membuat unit testing untuk method tsb.

package hello

import "testing"

func TestHello(t *testing.T) {
// test for empty result
emptyResult := Hello("")
if emptyResult != "name should not be empty" {
t.Errorf("failed, expected %v got %v", "name should not be empty", emptyResult)
}

// test for not empty result
notEmptyResult := Hello("Jhon Doe")
if notEmptyResult != "Jhon Doe" {
t.Errorf("failed, expected %v got %v", "Jhon Doe", emptyResult)
}
}

Terdapat 2 pengetesan dalam satu method TestHello dimana yang pertama kita test behavior ketika kita memasukan string kosong, dan kedua kita memasukan string yang tidak kosong. Selanjutnya kita cek apakah balikan dari method tersebut sama dengan yang kita deklarasikan, jika tidak akan mengembalikan error bahwa input tidak sesuai.

passed test

Kurang lebih seperti gambar diatas jika semua case-case yang kita masukan sesuai dengan behavior nya.

Unit Testing + Mocking menggunakan GoMock

Mocking merupakan cara pengujian perilaku sebuah service dengan cara membuat object palsu sehingga pengujian nya dapat terisolasi dari object yg sebenernya.

Disini kita akan menggunakan GoMock sebagai framework mocking dalam bahasa pemrograman golang.

Contoh Case Unit Testing + GoMock

Semisal kita memiliki sebuah service bernama task_service.go yg memiliki kebutuhan untuk berkomunikasi dengan repository.TaskRepositoryInterface

type TaskServiceInterface interface {
GetAllTask(ctx context.Context) (response []object.TaskObjResponse, err error)
CreateTask(ctx context.Context, req object.TaskObjRequest) error
UpdateTask(ctx context.Context, req object.TaskUpdateObjRequest) error
}
type taskService struct {
taskRepo repository.TaskRepositoryInterface
}
func NewTaskService(taskRepo repository.TaskRepositoryInterface) *taskService {
return &taskService{
taskRepo: taskRepo,
}
}
func (s *taskService) GetAllTask(ctx context.Context) (response []object.TaskObjResponse, err error) {
resp, err := s.taskRepo.GetAll(ctx)
if err != nil {
return response, err
}
for _, v := range resp {
obj := object.TaskObjResponse{
ID: int(v.ID),
Name: v.Name,
Status: v.Status,
CreatedAt: v.CreatedAt,
UpdatedAt: v.UpdatedAt,
}
response = append(response, obj)
}
return response, nil
}
func (s *taskService) CreateTask(ctx context.Context, req object.TaskObjRequest) error {
err := s.taskRepo.Create(ctx, req)
if err != nil {
return err
}
return nil
}
func (s *taskService) UpdateTask(ctx context.Context, req object.TaskUpdateObjRequest) error {
err := s.taskRepo.Update(ctx, req)
if err != nil {
return err
}
return nil
}

Mocking Repository

Untuk mengisolasi sebuah repository task_repository.go yaitu dengan melakukan mocking terhadap contract atau interface yg ada

type TaskRepositoryInterface interface {
GetAll(ctx context.Context) (data []model.Task, err error)
Create(ctx context.Context, req object.TaskObjRequest) error
Update(ctx context.Context, req object.TaskUpdateObjRequest) error
}

Dengan menggunakan mockgen --source=task_repository.go Maka hasil generate mock nya seperti ini.

package mocksimport (
context "context"
reflect "reflect"
gomock "github.com/golang/mock/gomock"
model "gitlab.warungpintar.co/sharing-session/practice-go-unit-test/app/model"
object "gitlab.warungpintar.co/sharing-session/practice-go-unit-test/app/object"
)
// MockTaskRepositoryInterface is a mock of TaskRepositoryInterface interface
type MockTaskRepositoryInterface struct {
ctrl *gomock.Controller
recorder *MockTaskRepositoryInterfaceMockRecorder
}
// MockTaskRepositoryInterfaceMockRecorder is the mock recorder for MockTaskRepositoryInterface
type MockTaskRepositoryInterfaceMockRecorder struct {
mock *MockTaskRepositoryInterface
}
// NewMockTaskRepositoryInterface creates a new mock instance
func NewMockTaskRepositoryInterface(ctrl *gomock.Controller) *MockTaskRepositoryInterface {
mock := &MockTaskRepositoryInterface{ctrl: ctrl}
mock.recorder = &MockTaskRepositoryInterfaceMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockTaskRepositoryInterface) EXPECT() *MockTaskRepositoryInterfaceMockRecorder {
return m.recorder
}
// GetAll mocks base method
func (m *MockTaskRepositoryInterface) GetAll(ctx context.Context) ([]model.Task, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAll", ctx)
ret0, _ := ret[0].([]model.Task)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetAll indicates an expected call of GetAll
func (mr *MockTaskRepositoryInterfaceMockRecorder) GetAll(ctx interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAll", reflect.TypeOf((*MockTaskRepositoryInterface)(nil).GetAll), ctx)
}
// Create mocks base method
func (m *MockTaskRepositoryInterface) Create(ctx context.Context, req object.TaskObjRequest) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Create", ctx, req)
ret0, _ := ret[0].(error)
return ret0
}
// Create indicates an expected call of Create
func (mr *MockTaskRepositoryInterfaceMockRecorder) Create(ctx, req interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockTaskRepositoryInterface)(nil).Create), ctx, req)
}
// Update mocks base method
func (m *MockTaskRepositoryInterface) Update(ctx context.Context, req object.TaskUpdateObjRequest) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Update", ctx, req)
ret0, _ := ret[0].(error)
return ret0
}
// Update indicates an expected call of Update
func (mr *MockTaskRepositoryInterfaceMockRecorder) Update(ctx, req interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockTaskRepositoryInterface)(nil).Update), ctx, req)
}

Implementasi unit test untuk service task_service_test.go dengan menggunakan data mocking

func Test_Get_Task(t *testing.T) {
t.Run("Should get task data", func(t *testing.T) {
ctrl := NewController(t)
defer ctrl.Finish()
mock := mockRepositories.NewMockTaskRepositoryInterface(ctrl)
expected := []model.Task{
model.Task{
ID: 0,
Name: "Testing 0",
Status: "todo",
CreatedAt: "2020-04-14T22:54:15Z",
UpdatedAt: "2020-04-14T22:54:15Z",
}, model.Task{
ID: 1,
Name: "Testing 1",
Status: "Doing",
CreatedAt: "2020-04-14T22:54:15Z",
UpdatedAt: "2020-04-14T22:54:15Z",
},
model.Task{
ID: 2,
Name: "Testing 0",
Status: "done",
CreatedAt: "2020-04-14T22:54:15Z",
UpdatedAt: "2020-04-14T22:54:15Z",
},
}
mock.EXPECT().GetAll(context.Background()).Return(expected, nil).Times(1) service := taskService{
taskRepo: mock,
}
result, err := service.GetAllTask(context.Background())
assert.Nil(t, err)
assert.Equal(t, int(expected[0].ID), result[0].ID)
assert.Equal(t, expected[0].Name, result[0].Name)
assert.Equal(t, expected[0].Status, result[0].Status)
assert.Equal(t, expected[0].CreatedAt, result[0].CreatedAt)
assert.Equal(t, expected[0].UpdatedAt, result[0].UpdatedAt)
})
}

Dengan seperti ini service yang membutuhkan object dari sebuah repository bisa di inject dengan mocking supaya terisolasi dari object yg sebenernya tetapi dengan perilaku yang sama.

Referensi :

Artikel Sebelumnya :

--

--

Okky Muhamad Budiman
Okky Muhamad Budiman

Written by Okky Muhamad Budiman

Tech Enthusiast, Punk Rock Software Engineer, Hustler Harder

Responses (1)