Unit Test: Mock Obat Pusing atau Yang Buat Pusing?
Tags: beginners, programming, tutorial, testing
Published: 2023-04-20 07:30:31

Kalo kita udah mulai ngerjain sebuah sistem, biasanya kita juga akan diminta untuk buat Unit Test-nya juga. Tapi agar bisa sebuah Unit Test bisa tercover dengan **baik** dan **mudah**, kita perlu belajar nih tentang **Mock**. Pastikan sebelum kalian belajar tentang dan cara menggunakan **Mock**, kalian sudah belajar menggunakan **Interface** dan konsep **Encapsulation**. Lebih baik lagi kalau kalian sudah belajar tentang **Clean Architecture**.

### What is Mock & How it works? 🙄

Kalian tau **stuntman**? Mock bisa kita analogikan seperti **stuntman** atau **stunt double** dalam dunia unit test. Seperti halnya stuntman yang menggantikan aktor asli dalam adegan berbahaya atau sulit, mock juga digunakan untuk menggantikan objek asli atau komponen eksternal yang bergantung pada code yang ingin diuji. Sehingga dengan adanya mock sebagai pengganti objek asli dalam pengujian, kita bisa tuh mengontrol perilaku dan respons yang diberikan oleh mock selama pengujian. Mirip banget dengan stuntman yang mengikuti skenario yang ditentuin oleh sutradara, mock juga bisa diatur untuk mengikuti skenario pengujian yang kita inginkan, sehingga kita bisa menguji code kita secara efisien dan efektif. 

Nah, jadi kalo misalkan kita mau membuat unit test pada sebuah code, yang mana didalam code itu ada kebutuhan untuk mengakses database. Kan pasti susah yak, kalau setiap kali kita jalanin unit test yang kita buat, kita harus siapkan database-nya juga. Karena itu kita tinggal buat aja mock untuk database ini, biar code yang kita buat enggak bener-bener berkomunikasi dengan database yang asli. 

### Before use Mock, take a notes about this 📝

Penggunaan mock enggak cuman buat seluruh code yang ada dependency ke luar code yang kalian buat. Oleh karena itu, pastiin kalian sudah belajar **Interface** dan **Encapsulation** karena mock bisa digunakan untuk code apapun yang sudah kita pisah-pisahkan menjadi komponen lebih kecil yang mana komunikasi antar code ini sudah menggunakan sebuah kontrak yang disebut **Interface**. Kalau kalian sudah belajar mengenai **Clean Architecture**, maka code kalian akan terpisah-pisah dengan baik tuh, salah satu layernya adalah code yang hanya berisi logika bisnis biasanya disebut *Usecase Layer*. Usecase ini nantinya akan punya mock sendiri yang akan dipakai di bagian *Handler Layer*, jadinya di *Handler* enggak perlu tuh bener-bener panggil *Usecase* yang isinya ada segudang logika panjang. Oleh karena itu kalian perlu paham akan struktur code kalian sendiri, yah meskipun gak jarang sih kita bingung pas lihat code kita sendiri. “Loh kok gini? Ini code maksudnya gimana yah😅”.

Setelah paham akan struktur code, kalian perlu untuk tahu apa yang ingin kalian uji. Kira-kira skenario apa yang ingin kalian buat dalam pengujian code kalian. Misalnya kalian punya code yang gunanya buat ambil data produk dari ecommerce. Nah salah satu skenario yang bisa kalian buat adalah jika website ecommerce yang akan kalian ambil data produknya tiba-tiba down, maka seharusnya code yang kita buat tidak crash dan hanya akan return error.

Kalo kalian jeli banget, skenario kalian biasanya akan banyak dan beragam banget. Dan itulah yang akan membuat kalian semakin pede dengan code kalian. Coba deh kalian baca dan bongkar dokumentasi seperti PRD atau Flowchart, biar kalian tau tuh “Oh nanti input-nya bakal kayak gini ya, terus dengan ekspektasi dari flow ini akan menghasilkan output A”. Pas kalian baca dokumentasi otomatis nih kalian punya kesempatan buat nemuin bug dari flow yang ada. Weee, tambah cakep lagi sistem yang kalian buat, kualitas meningkat dan semakin kredibel.

### Using Plain Mock (No library) 💪

Untuk pake mock di Golang, kita perlu menggunakan interface sebagai kontrak kerja akan sebuah proses. Di sini kita akan coba membuat mock untuk sebuah proses penyimpanan data ke database, dengan interface sebagai berikut

```go
type Database interface {
		Save(data string) error
}
```

Dengan interface diatas, mock yang bisa dibuat kurang lebih kayak gini

```go
type MockDatabase struct{
		SaveFunc func(data []byte) error
}

func (m *MockDatabase) Save(data string) error {
		return m.SaveFunc(data)
}
```

*SaveFunc* berfungsi agar kalian bisa memiliki kontrol akan perilaku dari mock. Kalo kalian gak ada *SaveFunc* di dalam struct MockDatabase sebenernya bisa juga mocknya berjalan, tapi perilakunya akan lebih statis. Misalnya contoh dibawah ini

```go
type MockDatabase struct{}

func (m *MockDatabase) Save(data string) error {
		if data == "test1" {
				return fmt.Errorf("error test1")
		}

		return nil
}
```

atau mungkin bakal lebih parah lagi kayak gini

```go
type MockDatabase1 struct{}

func (m *MockDatabase1) Save(data string) error {
		return fmt.Errorf("error test1")
}

type MockDatabase2 struct{}

func (m *MockDatabase2) Save(data string) error {
		return nil
}
```

Jadi ribet banget kan? Kurang efektif dan efisien juga. Nah makanya penggunaan *SaveFunc* disini krusial biar memudahkan kita dalam pembuatan unit test.

Untuk cara pakenya, kalian tinggal *define* MockDatabase dan isi deh *SaveFunc* sesuai kebutuhan. Nih contohnya

```go
func TestSaveData(t *testing.T) {
    mockDB := &MockDatabase{
        SaveFunc: func(data []byte) error {
            return nil
        },
    }
    
    // Jalanin code yang menggunakan mockDB.Save()
	// Misalnya
	// dep := saveDataImpl{
	// 	db: mockDB,
	// }
	// dep.SaveData("test1")
}
```

Gimana cakep kan? 😎

### Huh? Why we need to use library then?

Meskipun kalian bisa buat mock sendiri, tapi kalo kalian pake *library* atau *package* yang khusus untuk mock kalian bisa dapet sederet benefit:

1. Mengurangi *boilerplate*, iyesss *boilerplate* pasti bakal berkurang dan yang perlu kalian tahu kalian bisa melakukan proses pembuatan mock secara otomatis. Interface berubah? gak perlu takut lagi deh, tinggal jalanin beberapa *command* aja, nanti mocknya auto update deh. Aseekk!!
2. ***Maintain* jadi lebih gampang**, yak betul jadi lebih gampang buat kalian maintain, kenapa? Karena dengan menggunakan *dedicated package* kalian pastinya akan mempunyai mock yang standar entah dari segi penamaan ataupun style code.
3. **Meningkatkan *coverage test***, kok bisa naik coverage-nya? sebenernya nggak otomatis naik, cuman akan membantu kalian untuk lebih mudah memenuhi beberapa skenario yang susah untuk dibuat dengan menggunakan mock yang manual. Coba deh, kalau simulasinya lebih gampang pasti kalian bisa *handle* tuh *edge cases* yang ada di pikiran kalian.
4. **Integrasi yang lebih baik dengan testing framework lainnya**, kebanyakan *package mock* yang populer bisa diintegrasikan dengan mudah ke testing *framework* lainnya.

*Package* untuk mock itu ada buanyakk banget, coba kalian cari deh di [https://awesome-go.com/](https://awesome-go.com/). Tapi biar gak bingung kita ambil satu aja deh, dan yang akan kita pakai itu *package* mock dari golang langsung yak namanya GoMock ([https://github.com/golang/mock](https://github.com/golang/mock)). 

### Gomock - The Savior😇

Yok mari kita langsung cus kita coba pakai gomock, tapi masih menggunakan Interface yang sama.

```go
// Aku copy-in nih biar gak perlu scroll ke atas-atas :D
type Database interface {
		Save(data string) error
}
```

Sebelum itu kita perlu install yang namanya **mockgen** yang gunanya buat *generate* mock secara otomatis based on interface yang dibuat.

Kalo versi go kalian < 1.16

```go
GO111MODULE=on go get github.com/golang/mock/mockgen@v1.6.0
```

Kalo versi go kalian 1.16+

```go
go install github.com/golang/mock/mockgen@v1.6.0
```

Cara pakai **mockgen** cukup simple

```go
mockgen -package=[PACKAGE_NAME] -source=[INTERFACE_FILE_LOCATION] -destination=[MOCK_FILE_DESTINATION]
```

Untuk command “package” sendiri itu opsional karena itu tergantung struktur folder yang kalian pakai. Kalo kalian itu menggabungkan seluruh mock menjadi 1 folder sendiri, ya nantinya package name-nya jadi sama semua nantinya. Contohnya kayak dibawah

```go
mockgen -package=mock -source=infra/db.go -destination=mock/infra/db.go
```

> **TIPS** 💡
Better untuk kumpulin semua command mockgen ke satu file biar nanti kita bisa autogenerate seluruh interface yang ada di project kita. Kalo dari aku sendiri biasanya taruh ke ***SH File*** terus file ini nantinya akan dipanggil oleh ***Makefile***.
***SH File*** → [https://docs.fileformat.com/id/programming/sh/](https://docs.fileformat.com/id/programming/sh/)
***Makefile*** → [https://makefiletutorial.com/](https://makefiletutorial.com/)
> 

Setelah file mock sudah ter-*generate*, jangan lupa juga untuk install package gomock di project kalian

```go
go get github.com/golang/mock/gomock
```

```go
func TestSaveData(t *testing.T) {
    ctrl := gomock.NewController(t)
		defer ctrl.Finish()

		m := mock.NewMockDatabase(ctrl)
		m.EXPECT().Save("test1").Return(nil)

		// Jalanin code yang menggunakan mock database
		// Misalnya 
		// dep := saveDataImpl{
		// 	db: m,
		// }
		// dep.SaveData("lala")
}
```

Pastikan untuk selalu inisialisasi ekspektasi mock dengan rapi, kalo rekomendasi dari aku silahkan kalian gunakan *Table Driven* ya! Yang tau *Table Drive* bisa komen di bawah yak! Clue-nya kalo kalian pakai Visual Studio Code, *Table Drive* ini bisa di-generate loh tinggal klik kanan *Generate Unit Test* jadi deh skeleton unit test-nya.

### Gomock not good enough?😕

Selain gomock, ada beberapa library lain yang bisa kalian explore

1. [Testify](https://github.com/stretchr/testify), library ini sebenernya enggak cuman buat mock tapi juga bisa membuat unit test kita lebih baik dan readable yaitu dengan ***assert package***. Tapi ada kabar baik buat kalian, karena kalo kalian mau tetep pake testify tapi untuk mock-nya auto generated kalian tinggal pake 1 package tambahan yaitu [https://github.com/vektra/mockery](https://github.com/vektra/mockery)
2. Ginkgo, library ini cara pakai-nya agak berbeda dengan testify dan gomock. Tapi bagi kalian yang pernah menggunakan bahasa pemrograman lain mungkin akan cukup familiar dengan cara penulisannya karena mirip dengan beberapa library unit test lain seperti [Jasmine](https://jasmine.github.io/) dan [Quick](https://github.com/Quick/Quick). Karena Ginkgo ini terbilang *expressive* dan kadang-kadang juga dikategorikan sebagai **Behavior-Driven Development** (BDD)

### Fun Fact ✅

Kita ini manusia gampang banget untuk ngebuat error, jadi sebenernya banyak sekali bug atau dosa dari code itu gara-gara manusia. Ada sebuah penelitian yang menyatakan kalau “*human factor*” error itu nyata banget dan gak bisa dihindari. Nah kalian penasaran kan, seberapa sering sih kita melakukan error? Kalian bisa baca detailnya di [https://lifetime-reliability.com/tutorials/human_error_rate_table_insights/](https://lifetime-reliability.com/tutorials/human_error_rate_table_insights/). Itung-itung kalian belajar bahasa enggress yak, karena kalau mau hidup di dunia teknologi ya harus bisa bahasa inggris. Minimal kalau kita ada bug di code kita, kita bisa tanya tuh di stackoverflow.

Masih bingung? yuk kita diskusi di kolom komentar.

### Special Thanks 👏
Cover Image by [Kristijan Arsov](https://unsplash.com/pt-br/@aarsoph?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/s/photos/stuntman?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)