Unit test 的環境通常比較單純。不會存在程式所相依的 service 或指令。如 docker、mysql 等之類的 Service。
這邊紀錄如何使用 gomock 去 mock 其 interface。產生的 mock 檔案是如何應用於 unit test 撰寫。
DB 範例程式
GetNameByIndex 為 DB Interface 的一個 method。透過 index 來取得對應的 Name。
Build Mocks
官方有提到 Source mode 與 Reflect 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