Testing with GoMock: A Tutorial
08/16/17
This is a quick tutorial on how to test code using the GoMock mocking library and the standard library testing package testing
.
GoMock is a mock framework for Go. It enjoys a somewhat official status as part of the github.com/golang organization, integrates well with the built-in testing
package, and provides a flexible expectation API.
The code snippets referenced in this post are available on GitHub: github.com/sgreben/testing-with-gomock.
Contents
Installation
First, we need to install the gomock
package github.com/golang/mock/gomock
as well as the mockgen
code generation tool github.com/golang/mock/mockgen
. Technically, we could do without the code generation tool, but then we’d have to write our mocks by hand, which is tedious and error-prone.
Both packages can be installed using go get
:
go get github.com/golang/mock/gomock go get github.com/golang/mock/mockgen
We can verify that the mockgen
binary was installed successfully by running
$GOPATH/bin/mockgen
This should output usage information and a flag list. Now that we’re all set up, we’re ready to test some code!
Basic Usage
Usage of GoMock follows four basic steps:
- Use
mockgen
to generate a mock for the interface you wish to mock. - In your test, create an instance of
gomock.Controller
and pass it to your mock object’s constructor to obtain a mock object. - Call
EXPECT()
on your mocks to set up their expectations and return values - Call
Finish()
on the mock controller to assert the mock’s expectations
Let’s look at a small example to demonstrate the above workflow. To keep things simple, we’ll be looking at just two files — an interface Doer
in the file doer/doer.go
that we wish to mock and a struct User
in user/user.go
that uses the Doer
interface.
The interface that we wish to mock is just a couple of lines — it has a single method DoSomething
that does something with an int
and a string
and returns an error
:
doer/doer.go
package doer type Doer interface { DoSomething(int, string) error }
Here’s the code that we want to test while mocking out the Doer
interface:
user/user.go
package user import "github.com/sgreben/testing-with-gomock/doer" type User struct { Doer doer.Doer } func (u *User) Use() error { return u.Doer.DoSomething(123, "Hello GoMock") }
Our current project layout looks as follows:
'-- doer '-- doer.go '-- user '-- user.go |
We’ll put the mock for Doer
in a package mocks
in the root directory and the test for User
in the file user/user_test.go
:
'-- doer '-- doer.go '-- mocks '-- mock_doer.go '-- user '-- user.go '-- user_test.go |
We start by creating a directory mocks
that will contain our mock implementations and then running mockgen
on the doer
package:
mkdir -p mocks
mockgen -destination=mocks/mock_doer.go -package=mocks github.com/sgreben/testing-with-gomock/doer Doer
Here, we have to create the directory mocks
ourselves because GoMock won’t do it for us and will quit with an error instead. Here’s what the arguments given to mockgen
mean:
-destination=mocks/mock_doer.go
: put the generated mocks in the filemocks/mock_doer.go
.-package=mocks
: put the generated mocks in the packagemocks
github.com/sgreben/testing-with-gomock/doer
: generate mocks for this packageDoer
: generate mocks for this interface. This argument is required — we need to specify the interfaces to generate mocks for explicitly. We can, however specify multiple interfaces here as a comma-separated list (e.g.Doer1,Doer2
).
If $GOPATH/bin
was not in our $PATH
, we’d have to call mockgen
via $GOPATH/bin/mockgen
. In the following we’ll assume that we have $GOPATH/bin
in our $PATH
.
As a result, our invocation of mockgen
places a file mocks/mock_doer.go
in our project. This is how such a generated mock implementation looks:
mocks/mock_doer.go
// Code generated by MockGen. DO NOT EDIT. // Source: github.com/sgreben/testing-with-gomock/doer (interfaces: Doer) package mocks import ( gomock "github.com/golang/mock/gomock" ) // MockDoer is a mock of Doer interface type MockDoer struct { ctrl *gomock.Controller recorder *MockDoerMockRecorder } // MockDoerMockRecorder is the mock recorder for MockDoer type MockDoerMockRecorder struct { mock *MockDoer } // NewMockDoer creates a new mock instance func NewMockDoer(ctrl *gomock.Controller) *MockDoer { mock := &MockDoer{ctrl: ctrl} mock.recorder = &MockDoerMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use func (_m *MockDoer) EXPECT() *MockDoerMockRecorder { return _m.recorder } // DoSomething mocks base method func (_m *MockDoer) DoSomething(_param0 int, _param1 string) error { ret := _m.ctrl.Call(_m, "DoSomething", _param0, _param1) ret0, _ := ret[0].(error) return ret0 } // DoSomething indicates an expected call of DoSomething func (_mr *MockDoerMockRecorder) DoSomething(arg0, arg1 interface{}) *gomock.Call { return _mr.mock.ctrl.RecordCall(_mr.mock, "DoSomething", arg0, arg1) }
Note that the generated EXPECT()
method is defined on the same object as the mock methods (in this case, DoSomething
) — avoiding name clashes here is likely a reason for the non-standard all-uppercase name.
Next, we define a mock controller inside our test. A mock controller is responsible for tracking and asserting the expectations of its associated mock objects.
We can obtain a mock controller by passing a value t
of type *testing.T
to its constructor, and then use it to construct a mock of the Doer
interface. We also defer
its Finish
method — more on this later.
mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockDoer := mocks.NewMockDoer(mockCtrl)
Suppose we want to assert that mockerDoer
‘s Do
method will be called once, with 123
and "Hello GoMock"
as arguments, and will return nil
.
To do this, we can now call EXPECT()
on the mockDoer
to set up its expectations in our test. The call to EXPECT()
returns an object (called a mock recorder) providing methods of the same names as the real object.
Calling one of the methods on the mock recorder specifies an expected call with the given arguments. You can then chain other properties onto the call, such as:
- the return value (via
.Return(...)
) - the number of times this call is expected to occur (via
.Times(number)
, or via.MaxTimes(number)
and.MinTimes(number)
)
In our case, the call looks like this:
mockDoer.EXPECT().DoSomething(123, "Hello GoMock").Return(nil).Times(1)
That’s it – we’ve now specified our first mock call! Here’s the complete example:
user/user_test.go
package user_test import ( "github.com/sgreben/testing-with-gomock/mocks" "github.com/sgreben/testing-with-gomock/user" ) func TestUse(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockDoer := mocks.NewMockDoer(mockCtrl) testUser := &user.User{Doer:mockDoer} // Expect Do to be called once with 123 and "Hello GoMock" as parameters, and return nil from the mocked call. mockDoer.EXPECT().DoSomething(123, "Hello GoMock").Return(nil).Times(1) testUser.Use() }
Probably, it’s not obvious where in the code the mock’s expectations are asserted. This happens in the deferred Finish()
. It’s idiomatic to defer
this call to Finish
at the point of declaration of the mock controller — this way we don’t forget to assert the mock expectations later.
Finally, we’re ready to run our tests:
$ go test -v github.com/sgreben/testing-with-gomock/user
=== RUN TestUse
--- PASS: TestUse (0.00s)
PASS
ok github.com/sgreben/testing-with-gomock/user 0.007s
If you need to construct more than one mock, you can reuse the mock controller — its Finish
method will then assert the expectations of all mocks associated with the controller.
We might also want to assert that the value returned by the Use
method is indeed the one returned to it by DoSomething
. We can write another test, creating a dummy error and then specifying it as a return value for mockDoer.DoSomething
:
user/user_test.go
func TestUseReturnsErrorFromDo(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() dummyError := errors.New("dummy error") mockDoer := mocks.NewMockDoer(mockCtrl) testUser := &user.User{Doer:mockDoer} // Expect Do to be called once with 123 and "Hello GoMock" as parameters, and return dummyError from the mocked call. mockDoer.EXPECT().DoSomething(123, "Hello GoMock").Return(dummyError).Times(1) err := testUser.Use() if err != dummyError { t.Fail() } }
Using GoMock with go:generate
Running mockgen
for each package and interface individually is cumbersome when there is a large number of interfaces/packages to mock. To alleviate this problem, the mockgen
command may be placed in a special go:generate
comment.
In our example, we can add a go:generate
comment just below the package
statement of our doer.go
:
doer/doer.go
package doer //go:generate mockgen -destination=../mocks/mock_doer.go -package=mocks github.com/sgreben/testing-with-gomock/doer Doer type Doer interface { DoSomething(int, string) error }
Note that at the point where mockgen
is called, the current working directory is doer
— hence we need to specify ../mocks/
as the directory to write our mocks to, not just mocks/
.
We can now comfortably generate all mocks specified by such a comment by running
go generate ./...
from the project’s root directory. Note that there is no space between //
and go:generate
in the comment. This is required for go generate
to pick up the comment as an instruction to process.
A reasonable policy on where to put the go:generate
comment and which interfaces to include is the following:
- One
go:generate
comment per file containing interfaces to be mocked - Include all interfaces to generate mocks for in the call to
mockgen
- Put the mocks in a package
mocks
and write the mocks for a fileX.go
intomocks/mock_X.go
.
This way, the mockgen
call is close to the actual interfaces, while avoiding the overhead of separate calls and destination files for each interface.
Using argument matchers
Sometimes, you don’t care about the specific arguments a mock is called with. With GoMock, a parameter can be expected to have a fixed value (by specifying the value in the expected call) or it can be expected to match a predicate, called a Matcher. Matchers are used to represent ranges of expected arguments to a mocked method. The following matchers are pre-defined in GoMock:
gomock.Any()
: matches any value (of any type)gomock.Eq(x)
: uses reflection to match values that areDeepEqual
tox
gomock.Nil()
: matchesnil
gomock.Not(m)
: (wherem
is a Matcher) matches values not matched by the matcherm
gomock.Not(x)
: (wherex
is not a Matcher) matches values notDeepEqual
tox
For example, if we don’t care about the value of the first argument to Do
, we could write:
mockDoer.EXPECT().DoSomething(gomock.Any(), "Hello GoMock")
GoMock automatically converts arguments that are not of type Matcher
to Eq
matchers, so the above call is equivalent to:
mockDoer.EXPECT().DoSomething(gomock.Any(), gomock.Eq("Hello GoMock"))
You can define your own matchers by implementing the gomock.Matcher
interface:
gomock/matchers.go
(excerpt)
type Matcher interface { Matches(x interface{}) bool String() string }
The Matches
method is where the actual matching happens, while String
is used to generate human-readable output for failing tests. For example, a matcher checking an argument’s type could be implemented as follows:
match/oftype.go
package match import ( "reflect" "github.com/golang/mock/gomock" ) type ofType struct{ t string } func OfType(t string) gomock.Matcher { return &ofType{t} } func (o *ofType) Matches(x interface{}) bool { return reflect.TypeOf(x).String() == o.t } func (o *ofType) String() string { return "is of type " + o.t }
We can then use our custom matcher like this:
// Expect Do to be called once with 123 and any string as parameters, and return nil from the mocked call. mockDoer.EXPECT(). DoSomething(123, match.OfType("string")). Return(nil). Times(1)
We’ve split the above call across multiple lines for readability. For more complex mock calls this is a handy way of making the mock specification more readable. Note that in Go we have to put the dot at the end of each line in a sequence of chained calls. Otherwise, the parser will consider the line ended and we’ll get a syntax error.
Asserting call order
The order of calls to an object is often important. GoMock provides a way to assert that one call must happen after another call, the .After
method. For example,
callFirst := mockDoer.EXPECT().DoSomething(1, "first this") callA := mockDoer.EXPECT().DoSomething(2, "then this").After(callFirst) callB := mockDoer.EXPECT().DoSomething(2, "or this").After(callFirst)
specifies that callFirst
must occur before either callA
or callB
.
GoMock also provides a convenience function gomock.InOrder
to specify that the calls must be performed in the exact order given. This is less flexible than using .After
directly, but can make your tests more readable for longer sequences of calls:
gomock.InOrder( mockDoer.EXPECT().DoSomething(1, "first this"), mockDoer.EXPECT().DoSomething(2, "then this"), mockDoer.EXPECT().DoSomething(3, "then this"), mockDoer.EXPECT().DoSomething(4, "finally this"), )
Under the hood, InOrder
uses .After
to chain the calls in sequence.
Specifying mock actions
Mock objects differ from real implementations in that they don’t implement any of their behavior — all they do is provide canned responses at the appropriate moment and record their calls. However, sometimes you need your mocks to do more than that. Here, GoMock‘s Do
actions come in handy. Any call may be decorated with an action by calling .Do
on the call with a function to be executed whenever the call is matched:
mockDoer.EXPECT(). DoSomething(gomock.Any(), gomock.Any()). Return(nil). Do(func(x int, y string) { fmt.Println("Called with x =",x,"and y =", y) })
Complex assertions about the call arguments can be written inside Do
actions. For example, if the first (int
) argument of DoSomething
should be less than or equal to the length of the second (string
) argument, we can write:
mockDoer.EXPECT(). DoSomething(gomock.Any(), gomock.Any()). Return(nil). Do(func(x int, y string) { if x > len(y) { t.Fail() } })
The same functionality could not be implemented using custom matchers, since we are relating the concrete values, whereas matchers only have access to one argument at a time.
Summary
In this post, we’ve seen how to generate mocks using mockgen
and how to batch mock generation using go:generate
comments and the go generate
tool. We’ve covered the expectation API, including argument matchers, call frequency, call order and Do
-actions.
If you have any questions or if you feel that there’s something missing or unclear, please don’t hesitate to let me know in the comments!
相關推薦
Testing with GoMock: A Tutorial
08/16/17 This is a quick tutorial on how to test code using the GoMock mocking library and the standard library testing package testing.GoMock is a mock f
Microsoft Azure Tutorial: Build your first movie inventory web app with just a few lines of code
tro options core any call jpg should nav lines Editor’s Note: The following is a guest post from Mustafa Mahmutovi?, a Microsoft Student
Pytorch Tutorial (1) -- DEEP LEARNING WITH PYTORCH: A 60 MINUTE BLITZ
官方網頁:https://pytorch.org/tutorials/beginner/deep_learning_60min_blitz.html#deep-learning-with-pytorch-a-60-minute-blitz 一、安裝torchvision conda
Pytorch Tutorial (1) -- DEEP LEARNING WITH PYTORCH: A 60 MINUTE BLITZ - Markdown版本
Deep Learning with PyTorch: A 60 Minute Blitz 0.基礎準備 1.安裝torchvision 2.更新了一堆,以下是torchvision文件 1.What is PyTorch?
DIY Deep Learning for Vision: A Tutorial With Caffe 報告筆記
目錄 簡介 要點記錄 提問 總結 簡介 報告時間是北京時間 12月14日 凌晨一點到兩點,主講人是 Caffe 團隊的核心之一 Evan Shelhamer。第一次用 GoToMeeting 參加視訊會議,效果真是不錯。 報告後分享出了 視訊 和 展示檔案。另一講座
[Node & Testing] Intergration Testing with Node Express
ade ood oca top into which .post find sync We have express app: import _ from ‘lodash‘ import faker from ‘faker‘ import express from
Building and Testing with Gradle筆記3——Ant and Gradle
Hello Ant 可以將Ant理解為Java世界的make,正如使用make需要編寫Makefile。Ant也有類似Makefile的構建檔案,只不過是使用xml來描述的。建立一個檔案build.xml,內容如下: <project> <target
Building and Testing with Gradle筆記2——Gradle Tasks
宣告一個Task task hello 執行gradle tasks輸出當前Project中所有task :tasks ------------------------------------------------------------ All tasks runnab
Building and Testing with Gradle筆記1——Hello,Gradle
導言 Gradle構建檔案是基於Groovy的DSL編寫而成 安裝 下載Gradle壓縮包並解壓,新增GRADLE_HOME環境變數指向安裝路徑。並將bin目錄新增到Path中。 構建檔案的Hello World 新建build.gradle檔案,內容如下 tas
ValueError: Only call `sparse_softmax_cross_entropy_with_logits` with named a
entropy spa initial bsp sof 網絡 label ria value 第五章中完整的訓練MNIST數據的神經網絡模型的程序代碼中,直接運行程序的話會遇到以下的錯誤。 把下面的這行代碼 # 計算交叉熵及其平均值 cross_entropy = t
讀書 |《金礦III:精益領導者的軟實力》Lead with Respect: A Novel of Lean Practice
目錄: 第1章 領導到基層去學習 第2章 將成功與價值掛鉤 第3章 員工自主解決問題的管理方式 第4章 人人蔘與改善 第5章 學習的真諦 第6章 培養領導幹部 第7章 加強團隊合作 &nbs
java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this a
在設定隱藏標題欄的時候:出現以下錯誤 java.lang.IllegalStateException: You need to use a Theme. AppCompat theme (or des
RN WebView Cannot Add a child that doesn’t have a YogaNode to a parent with out a measure function
解決方案1: 檢查 return 方法內是否存在註釋,如果有全部刪除。 解決方案2: 如果使用了WebView,那麼請刪除外層的View檢視,否則在Android上可能出現這種錯誤。 render() { const { params } = this.
A tutorial on binary descriptors – part 2 – The BRIEF descriptor(轉)
A tutorial on binary descriptors – part 2 – The BRIEF descriptor Following the previous posts that provided both an introduction to patch
[翻譯]Unit testing with JUnit 4.x and EasyMock in Eclipse
譯文: 在Eclipse中使用Junit4.x與EasyMock進行單元測試 摘要 這篇文章簡單講解了如何在Eclipse中利用Junit 4.x和EasyMock進行單元測試。 當你閱讀完這篇文章後,可以在Eclipse中使用JUnit進行單元測試。 1. 概要 1.1. 單元測試
Clean architecture for Android with Kotlin: a pragmatic approach
Clean architecture is a topic that never gets old in the Android world, and from the comments and questions I receive, I feel it’s still not very cle
Swarm testing with Locust.io for the first time
Swarm testing with Locust.io for the first timeA beginners guideWhen you build services for the web you want to make sure they don’t fall over completely w
How can you best eliminate bias when conducting a usability testing analysis or a user inquiry /…
Let’s start at the beginning with a definition of bias. The Merriam-Webster says it is:“[a] systematic error introduced into sampling or testing by selecti
Setup Unit Testing with TFS 2015
Setup Unit Testing with TFS 2015Create unit test project in solutionTo get started with Unit Testing your Project along with the DevOps Process, we need to
Turn anything into a Pokemon with Hipception, a new photo decoration trend. Download FREE now from App Store and Google Play Sto
After being an outsource developer and also writing the article related to application development techniques for a big while. Now it's t