Golang : Unit Test with Mocking Technique
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.
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 :