Install
GoMock
It is a Go framework. It integrates well with the built-in test package and provides flexibility when unit testing. As we know, unit testing code with external resources (databases, networks, and files) or dependencies is always cumbersome.
In order to use GoMock, we need to installgomock
Bag/golang/mock/gomock
andmockgen
Code Generation Tool/golang/mock/mockgen
. Use this command line:
go get /golang/mock/gomock go get /golang/mock/mockgen
The use of GoMock follows four basic steps:
- use
mockgen
Generate a mock object for the interface you want to simulate. - In the test section, create a
and pass it to yours
mock
The constructor of the object to obtain amock
Object. - Called on mock
EXPECT()
to set the expected value and return value. - Called on the analog controller
Finish()
To assert the expectation of the mock object.
start
Let's create a folder like this (this code is executed under go1.16.15 version)
gomock_test ├── doer │ └── ├── mocks │ └── mock_doer.go └── user ├── └── user_test.go
doer/
package doer type Doer interface { DoSomething(int, string) error SaySomething(string) string }
So here is the code we want to test when mocking the Doer interface.
user/
package user import "gomock_test/doer" const ( filtered = "OK" unfiltered = "spam" Nice = "nice" Bad = "bad" ) type User struct { // struct while mocking the doer interface Doer } // method Use using it func (u *User) Use() error { return (123, "Hello GoMock") } func (u *User) SaySomething(num int) string { if num == 3 { return (unfiltered) } return (filtered) } type Student struct{} func (s *Student) DoSomething(_ int, _ string) error { panic("not implemented") // TODO: Implement } func (s *Student) SaySomething(kata string) string { if kata == filtered { return Nice } return Bad }
We willDoer
Simulation in one packagemocks
middle. We first create a directory containing our mock implementationmocks
, and thendoer
Run on the packagemockgen
:
mockgen -destination=../mocks/mock_doer.go -package=mocks gomock_test/doer Doer
NOTE: When executing this step, an error will be reported: Failed to format generated source code: mocks/mock_doer.go:5:15: expected ';', found '.’ At this time, we only need to copy the printed code to our corresponding file.
Running mockgen for each package and interface is a utopia when there are a large number of interfaces/packages that need to be simulated. To alleviate this problem,mockgen
Command andgo:generate
Put it together.
go:generate mockgen -destination=../mocks/mock_doer.go -package=mocks gomock_test/doer Doer
We have to create the directory mock ourselves, because GoMock won't do this for us, but will exit with an error.
- destination=.../mocks/mock_doer.go: Put the generated mocks in this path.
- -package=mocks: Put the generated mocks in this package
- gomock_test/doer: Generate mocks for this package.
- Doer: Generate mocks for this interface (required) because we need to specify which interface to generate mocks. (If necessary, multiple interfaces can be specified with a comma-separated list. For example, Doer1, Doer2)
Because we'remockgen
The call placed a file in our projectmocks/mock_doer.go
. This is how it is generatedmock
What the implementation looks like:
// Code generated by MockGen. DO NOT EDIT. // Source: /timliudream/go-test/gomock_test/doer (interfaces: Doer) // Package /timliudream/go-test/gomock_test/mocks is a generated GoMock package. package mocks import ( gomock "/golang/mock/gomock" reflect "reflect" ) // MockDoer is a mock of Doer interface. type MockDoer struct { ctrl * recorder *MockDoerMockRecorder } // MockDoerMockRecorder is the mock recorder for MockDoer. type MockDoerMockRecorder struct { mock *MockDoer } // NewMockDoer creates a new mock instance. func NewMockDoer(ctrl *) *MockDoer { mock := &MockDoer{ctrl: ctrl} = &MockDoerMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. func (m *MockDoer) EXPECT() *MockDoerMockRecorder { return } // DoSomething mocks base method. func (m *MockDoer) DoSomething(arg0 int, arg1 string) error { () ret := (m, "DoSomething", arg0, arg1) ret0, _ := ret[0].(error) return ret0 } // DoSomething indicates an expected call of DoSomething. func (mr *MockDoerMockRecorder) DoSomething(arg0, arg1 interface{}) * { () return (, "DoSomething", ((*MockDoer)(nil).DoSomething), arg0, arg1) } // SaySomething mocks base method. func (m *MockDoer) SaySomething(arg0 string) string { () ret := (m, "SaySomething", arg0) ret0, _ := ret[0].(string) return ret0 } // SaySomething indicates an expected call of SaySomething. func (mr *MockDoerMockRecorder) SaySomething(arg0 interface{}) * { () return (, "SaySomething", ((*MockDoer)(nil).SaySomething), arg0) }
Next, we define an analog controller in the test. A simulation controller is responsible for tracking and asserting the expectations of its associated simulation objects. We can pass a*
The value of type gives it the constructor to obtain a simulation controller and use it to construct aDoer
Simulated object of the interface.
//core of gomock mockCtrl := (t) //used to trigger final assertions. if its ignored, mocking assertions will never fail defer () // create a new mock object, passing the controller instance as parameter // for a newly created mock object it will accept any input and outpuite // need to define its behavior with the method expect mockDoer := (mockCtrl)
Use parameter matcher
In GoMock, a parameter can be expected to have a fixed value or can be expected to match a predicate (called a matcher). A matcher is used to represent the expected parameter range of the method being simulated. The following matchers are predefined in Gomock:
- () : Match any value (any type).
- (x) : Use reflection to match values equal to the x depth.
- () : Match nil
user/user_test.go
package user import ( "fmt" "/golang/mock/gomock" "gomock_test/mocks" "testing" ) func TestUse(t *) { //core of gomock mockCtrl := (t) //used to trigger final assertions. if its ignored, mocking assertions will never fail defer () // create a new mock object, passing the controller instance as parameter // for a newly created mock object it will accept any input and outpuite // need to define its behavior with the method expect mockDoer := (mockCtrl) testUser := &User{Doer: mockDoer} // // Expect Do to be called once with 123 and "Hello GoMock" as parameters, and return nil from the mocked call. ().DoSomething(123, "Hello GoMock").Return(nil).Times(1) (()) () } func TestUser_SaySomething(t *) { mockCtrl := (t) defer () mockDoer := (mockCtrl) user := User{ Doer: mockDoer, } type args struct { num int } tests := []struct { name string args args want string expect func() wantErr bool }{ { name: "Positive test case 1", expect: func() { ().SaySomething("spam").Return("bad") }, args: args{num: 3}, wantErr: false, want: "bad", }, } for _, tt := range tests { (, func(t *) { () if got := (); (got != ) != { ("gott :", got) ("() = %v, want %v", got, ) } }) } }
And the result of the unit test will be like this:
=== RUN TestUser_SaySomething
=== RUN TestUser_SaySomething/Positive_test_case_1
--- PASS: TestUser_SaySomething (0.00s)
--- PASS: TestUser_SaySomething/Positive_test_case_1 (0.00s)
PASS
ok /tokopedia/go_learning/udemy/pzn/gomock_test/user 1.100s
Talk about experience
Unit testing can be performed without calling external dependencies. usemocking
Framework can help us in many ways, building clean and lightweight unit tests. Combined with interface and dependency injection.
The above is the detailed explanation of the installation and use of GoMock in go language. For more information about Go language GoMock installation, please follow my other related articles!