使用 gomock 於 Unit test

Yi Jyun
5 min readJul 3, 2021

Unit test 的環境通常比較單純。不會存在程式所相依的 service 或指令。如 docker、mysql 等之類的 Service。

這邊紀錄如何使用 gomock 去 mock 其 interface。產生的 mock 檔案是如何應用於 unit test 撰寫。

DB 範例程式

GetNameByIndex 為 DB Interface 的一個 method。透過 index 來取得對應的 Name。

Build Mocks

官方有提到 Source modeReflect mode 這兩種方式去產生 mock 檔案

一開始先用 go mod 建立專案

# go mod
$ go mod init db

接著再透過 source mode 或 reflect mode 去產生 mock 相關的檔案。reflect mode 的好處是可以針對特定的 interfaces 去 mock.

# Source mode generates mock interfaces from a source file.
$ mockgen -destination db_mock.go -package db -source db.go
# Reflect mode generates mock interfaces by building a program that uses reflection to understand interfaces.
$ mockgen -destination db_mock.go -package db db DB

產生的 mock 檔案如下。

撰寫 unit test

一開始先產生 gomock controller

ctrl := gomock.NewController(t) 
defer ctrl.Finish()

接著產生 mock DB 的 struct type 的物件。再根據 GetNameByIndex 去設定帶入的 paramter 與 return 回傳的數值。這邊範例,只要帶入的 index 為 3,則回傳 superman 這個字串。

m := NewMockDB(ctrl) 
m.
EXPECT().
GetNameByIndex(gomock.Eq(3)).
Return("superman")

設置完畢後,把 m 與 index 變數丟入 GetName function。在判斷回傳的結果是否為預期。

index := 3 
wantName := "superman"
name := GetName(m, index)
assert.Equal(t, wantName, name, "they should be equal")

完整的 unit test 如下。

Type Matcher & Type Call

m.
EXPECT().
GetNameByIndex(gomock.Eq(3)).
Return("superman")

Type Matcher

這邊 gomock.Eq(3) 是 gomock 裡面的 Type Matcher。而 Matcher 有其他方法,可以針對需求去做使用。

type Matcher
func All(ms ...Matcher) Matcher
func Any() Matcher
func AssignableToTypeOf(x interface{}) Matcher
func Eq(x interface{}) Matcher
func GotFormatterAdapter(s GotFormatter, m Matcher) Matcher
func InAnyOrder(x interface{}) Matcher
func Len(i int) Matcher
func Nil() Matcher
func Not(x interface{}) Matcher
func WantFormatter(s fmt.Stringer, m Matcher) Matcher

Type Call

這邊 .Return(“superman”) 是 gomock 裡面的 Type Call。這邊 Call 有其他方法,可以針對需求去使用。

type Call
func (c *Call) After(preReq *Call) *Call
func (c *Call) AnyTimes() *Call
func (c *Call) Do(f interface{}) *Call
func (c *Call) DoAndReturn(f interface{}) *Call
func (c *Call) MaxTimes(n int) *Call
func (c *Call) MinTimes(n int) *Call
func (c *Call) Return(rets ...interface{}) *Call
func (c *Call) SetArg(n int, value interface{}) *Call
func (c *Call) String() string
func (c *Call) Times(n int) *Call

以下是使用不同的 method 的 unit test 範例。比較要注意的是,m 物件預設只能呼叫一次。以下範例使用 Anytimes() 來去除限制。

Reference

[1] Gomock github

[2] Gomock API doc

--

--