An example of an mock test using gomock

  • 2020-06-23 00:36:54
  • OfStack

In the development process, it is often necessary to cooperate with unit tests. However, in many cases, unit tests need to rely on some complicated preparations, such as database environment and network environment, and unit tests become a very troublesome thing. For example, let's say we need to request a web page and process the requested data. In the beginning, I usually start with a simple http service and then run my unit tests. However, this unit test seems very cumbersome to test. Even during continuous integration, I wrote a script to automatically start the service in order to automate the tests. Things seem to need to change.

mock objects were created to solve the above problem. mock objects can simulate the functions of actual dependent objects without any complicated preparation. All you need to do is define the object interface, implement it, and give it to the test object to use.

go-mock is an mock library developed specifically for the go language. It is a rare and good tool because of its simple usage and support for automatic code generation. Let me show you briefly how ES12en-ES13en works:

The first thing you need to do is to download the dependencies locally:


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

The first is code dependencies, and the second is command-line tools (which are especially handy).

Here's a very simple example of how gomock works:

I am in $GOPATH/src Create 1 new project under the directory: hellomock In the $GOPATH/src/hellomock New directory hellomock.go , and define an interface Talker :


package hellomock

type Talker interface {
  SayHello(word string)(response string)
}

Then we need a structure that implements the Talker function, and let's say we have a scenario where we now have 1 greeter position, and we need 1 greeter who can greet, and of course this greeter can be a person, or it can be a parrot. So what we need to do is define an Persion structure (or parrot or whatever) and implement the Talker interface:

person.go


package hellomock

import "fmt"

type Person struct{
 name string
}

func NewPerson(name string)*Person{
 return &Person{
   name:name,
 }
}


func (p *Person)SayHello(name string)(word string) {
 return fmt.Sprintf("Hello %s, welcome come to our company, my name is %s",name,p.name)  
}

Now our Person It's already done Talker Interface, now let's put him to work!

Now let's say we have a company, and the company has a greeter, which is our receptionist, and she does it Talker Interface. She can come to the guest automatically SayHello :

company.go


package hellomock

type Company struct {
 Usher Talker
}

func NewCompany(t Talker) *Company{
 return &Company{
  Usher:t,
 }
}

func ( c *Company) Meeting(gusetName string)string{
 return c.Usher.SayHello(gusetName)
}

Our scenario is already designed, so how would we traditionally implement unit testing?

company_test.go


package hellomock

import "testing"

func TestCompany_Meeting(t *testing.T) {
  person := NewPerson(" Waunny beauty ")
  company := NewCompany(person)
  t.Log(company.Meeting(" Wang nima "))
}

Testing:


/usr/local/go/bin/go test -v hellomock -run ^TestCompany_Meeting$

  company_test.go:8: Hello  Wang nima , welcome come to our company, my name is  Waunny beauty 

ok   hellomock  0.013s

Now it's easy to construct a Wang Nimei, but we're going to simulate it with an mock object, and mockgen comes in:

[

➜ hellomock > mkdir mock
➜ hellomock > mockgen -source=hellomock.go > mock/mock_Talker.go

]

At this point, it's going to generate mock/mock_Talker.go File:

It is important to note that the automatically generated files have the same source files in a different package, which requires a new directory to store

We don't need to care about the contents of the generated file, we just need to know how to use it

mock_Talker.go


// Automatically generated by MockGen. DO NOT EDIT!
// Source: hellomock.go

package mock_hellomock

import (
  gomock "github.com/golang/mock/gomock"
)

// MockTalker is a mock of Talker interface
type MockTalker struct {
  ctrl   *gomock.Controller
  recorder *MockTalkerMockRecorder
}

// MockTalkerMockRecorder is the mock recorder for MockTalker
type MockTalkerMockRecorder struct {
  mock *MockTalker
}

// NewMockTalker creates a new mock instance
func NewMockTalker(ctrl *gomock.Controller) *MockTalker {
  mock := &MockTalker{ctrl: ctrl}
  mock.recorder = &MockTalkerMockRecorder{mock}
  return mock
}

// EXPECT returns an object that allows the caller to indicate expected use
func (_m *MockTalker) EXPECT() *MockTalkerMockRecorder {
  return _m.recorder
}

// SayHello mocks base method
func (_m *MockTalker) SayHello(name string) string {
  ret := _m.ctrl.Call(_m, "SayHello", name)
  ret0, _ := ret[0].(string)
  return ret0
}

// SayHello indicates an expected call of SayHello
func (_mr *MockTalkerMockRecorder) SayHello(arg0 interface{}) *gomock.Call {
  return _mr.mock.ctrl.RecordCall(_mr.mock, "SayHello", arg0)
}

Let's see how to use this mock object. Create a new unit test:


func TestCompany_Meeting2(t *testing.T) {
  ctl := gomock.NewController(t)
  mock_talker := mock_hellomock.NewMockTalker(ctl)
  mock_talker.EXPECT().SayHello(gomock.Eq(" Wang nima ")).Return(" This is a custom return value and can be of any type. ")

  company := NewCompany(mock_talker)
  t.Log(company.Meeting(" Wang nima "))
  //t.Log(company.Meeting(" Zhang Quan eggs "))
}

Testing:


/usr/local/go/bin/go test -v hellomock -run ^TestCompany_Meeting2$
  company_test.go:21:  This is a custom return value and can be of any type. 
ok   hellomock  0.015s

As you can see, this returns the return value we defined on the mock object.

One point to note is that the SayHello of mock object can accept gomock.Eq (x interface{}) and gomock.Any (). The first one requires that the test case feed strictly conforms to x, while the second one allows arbitrary parameters to be passed in. For example, we passed "zhang Quan egg" in the commented out test, the result was wrong, the test failed:


/usr/local/go/bin/go test -v hellomock -run ^TestCompany_Meeting2$
  controller.go:113: no matching expected call: *mock_hellomock.MockTalker.SayHello([ Zhang Quan eggs ])
exit status 1
FAIL  hellomock  0.007s

As a primer, there are many more advanced USES of gomock that I hope you will explore on your own.

Reference article:

https://github.com/golang/mock/blob/master/README.md

https://github.com/grpc/grpc-go/blob/master/Documentation/gomock-example.md