1024programmer Java Example of how to use Gomock for unit testing

Example of how to use Gomock for unit testing

In the development process, it is often necessary to cooperate with unit testing. However, many times, unit testing needs to rely on some more complex preparations, such as relying on the database environment and the network environment. Unit testing becomes a very troublesome task. matter. For example, we need to request a web page and process the data returned by the request. When starting out, I usually start a simple http service and then run my unit tests. But this unit test seems to be very cumbersome to test. Even during the continuous integration process, in order to automate testing, I wrote a script to automatically start the corresponding service. Things seemed to need to change a bit.

Mock objects were born to solve the above problems. Mock (simulation) objects can simulate the functions of actual dependent objects without requiring very complicated preparations. All you need to do is to define the object interface, and then Implement it and give it to the test subjects to use.

go-mock is a mock library specially developed for the Go language. The library is simple to use and supports automatic code generation. It can be said to be a rare and good tool.

Foreword

In actual projects, when unit testing is required. But often find that there are a lot of dependencies. This is the time for Gomock to show its talents

Gomock is a mock framework for the Go language, the official one 🤪

Installation

 $ go get -u github.com/golang/mock/gomock
 $ go install github.com/golang/mock/mockgen
 

1. Step one: We will install the gomock third-party library and the mock code generation tool mockgen. The latter can greatly save our workload. You just need to understand how to use it

2. Step 2: Enter mockgen to verify whether the code generation tool is installed correctly. If it cannot respond normally, please check whether the bin directory contains the binary file

Usage

In the mockgen command, two generation modes are supported:

1. Source: Generate mock interface from source file (enabled by -source)

 mockgen -source=foo.go [other options]

2. reflect: Generate mock interface by using reflection program. It is enabled by passing two non-flag arguments: the import path and a comma-separated list of interfaces

 mockgen database/sql/driver Conn,Driver

Essentially speaking, there is no difference between the mock code generated by the two methods. So just choose the right one

Write test cases

In this article, we will simulate a simple Demo to write test cases and become familiar with the overall testing process

Steps

  1. Think clearly about the overall logic
  2. Define the interface for which you want to (mock) the dependency
  3. Use the mockgen command to generate mock files for the required mock interface
  4. Write unit test logic and use mocks in tests
  5. Verification of unit tests

Table of Contents

 ├── mock
 ├── person
 │ └── male.go
 └── user
   ├── user.go
   └── user_test.go

Writing

interface methods

Open the person/male.go file and write the following content:

 package person

 type Male interface {
   Get(id int64) error
 }

 

Call method

Open the user/user.go file and write the following content:

 package user

 import "github.com/EDDYCJY/mockd/person"

 typeUser struct {
   Person person.Male
 }

 func NewUser(p person.Male) *User {
   return &User{Person: p}
 }

 func (u *User) GetUserInfo(id int64) error {
   return u.Person.Get(id)
 }

 

Generate mock files

Go back to the root directory of mockd/ and execute the following command

The code is as follows: $ mockgen -source=./person/male.go -destination=./mock/male_mock.go -package=mock

After the execution is completed, you can find that there is an additional male_mock.go file in the mock/ directory, which is the mock file. So what are the uses of the instructions in the command? As follows:

  • -source: Set the interface file that needs to be simulated (mock)
  • -destination: Set the place where the mock file is output. If not set, it will be printed to the standard output
  • -package: Set the package name of the mock file. If not set, it will be mock_ prefix plus the file name (for example, the package name in this article will be mock_person)

For more instructions, please refer to the official documentation

Output mock file

 // Code generated by MockGen. DO NOT EDIT.
 // Source: ./person/male.go

 // Package mock is a generated GoMock package.
 package mock

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

 // MockMale is a mock of Male interface
 type MockMale struct {
   ctrl *gomock.Controller
   recorder *MockMaleMockRecorder
 }

 // MockMaleMockRecorder is the mock recorder for MockMale
 type MockMaleMockRecorder struct {
   mock *MockMale
 }

 // NewMockMale creates a new mock instance
 func NewMockMale(ctrl *gomock.Controller) *MockMale {
   mock := &MockMale{ctrl: ctrl}
   mock.recorder = &MockMaleMockRecorder{mock}
   return mock
 }

 // EXPECT returns an object that allows the caller to indicate expected use
 func (m *MockMale) EXPECT() *MockMaleMockRecorder {
   return m.recorder
 }

 // Get mocks base method
 func (m *MockMale) Get(id int64) error {
   ret := m.ctrl.Call(m, "Get", id)
   ret0, _ := ret[0].(error)
   return ret0
 }

 // Get indicates an expected call of Get
 func (mr *MockMaleMockRecorder) Get(id interface{}) *gomock.Call {
   return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockMale)(nil).Get), id)
 }

 

Test Cases

Open the user/user_test.go file and write the following content:

 package user

 import (
   "testing"

   "github.com/EDDYCJY/mockd/mock"

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

 func TestUser_GetUserInfo(t *testing.T) {
   ctl := gomock.NewController(t)
   defer ctl.Finish()

   var id int64 = 1
   mockMale := mock.NewMockMale(ctl)
   gomock.InOrder(
     mockMale.EXPECT().Get(id).Return(nil),
   )

   user := NewUser(mockMale)
   err := user.GetUserInfo(id)
   if err != nil {
     t.Errorf("user.GetUserInfo err: %v", err)
   }
 }

 
  1. gomock.NewController: Returns gomock.Controller, which represents the top-level control in the mock ecosystem. Defines the scope, life cycle and expected value of the mock object. Additionally it is safe in multiple goroutines
  2. mock.NewMockMale: Create a new mock instance
  3. gomock.InOrder: declares that given calls should be made in order (a secondary encapsulation of gomock.After)
  4. mockMale.EXPECT().Get(id).Return(nil): There are three steps here. EXPECT() returns an object that allows the caller to set expectations and return values. Get(id) sets the input parameters and calls the method in the mock instance. Return(nil) is to set the parameters of the previously called method. To put it simply, it means setting the input parameters and calling, and finally setting the return value
  5. NewUser(mockMale): Create a User instance. It is worth noting that the mock object is injected here, so it is actually used in subsequent user.GetUserInfo(id) calls (input parameter: id is 1). It calls the mock method we simulated in advance
  6. ctl.Finish(): Make the expected value assertion of the mock use case. Generally, defer is used to delay execution to prevent us from forgetting this operation

Test

Go back to the root directory of mockd/ and execute the following command

 $ go test ./user
 ok github.com/EDDYCJY/mockd/user

When you see this result, you’re done! You can adjust the return value of Return() yourself to get different test results 😄

View test status

Test Coverage

 $ go test -cover ./user
 ok github.com/EDDYCJY/mockd/user (cached) coverage: 100.0% of statements
 

You can turn on coverage statistics by setting the -cover flag, and the displayed content is coverage: 100.0%.

Visual interface

1. Generate test coverage profile files

 $ go test ./... -coverprofile=cover.out

2. Use profile files to generate visual interfaces

 $ go tool cover -html=cover.out

3. View the visual interface and analyze coverage

More

1. Commonly used mock methods

Call method

  • Call.Do(): declares the operation to be run when matched
  • Call.DoAndReturn(): declares the operation to be run when the matching call is made, and simulates the return value of the function
  • Call.MaxTimes(): Set the maximum number of calls to n times
  • Call.MinTimes(): Set the minimum number of calls to n times
  • Call.AnyTimes(): Allows 0 or more calls
  • Call.Times(): Set the number of calls to n times

Parameter matching

  • gomock.Any(): Match any value
  • gomock.Eq(): matches the specified type value through reflection without manual setting
  • gomock.Nil(): returns nil

For more suggested methods, please refer to the official documentation

2. Generate multiple mock files

You may want to generate mock files one by one, but it will crash?

Of course, the official provides a more convenient way, we can use go:generate to complete the batch processing function

 go generate [-run regexp] [-n] [-v] [-x] [build flags] [file.go... | packages]

Modify interface method

Open the person/male.go file and modify it to the following content:

 package person

 //go:generate mockgen -destination=../mock/male_mock.go -package=mock github.com/EDDYCJY/mockd/person Male

 type Male interface {
   Get(id int64) error
 }

 

We pay attention to the go:generate statement, which can be divided into the following parts:

  1. Declaration //go:generate (be careful not to leave spaces)
  2. Use mockgen command
  3. Definition -destination
  4. Definition -package
  5. Define source, here is the package path of person
  6. Define interfaces, here is Male

Regenerate mock files

Go back to the root directory of mockd/ and execute the following command

 $ go generate ./...

Check mock/ again and find that it has been generated correctly. Isn’t it very convenient when using multiple files? 🤩

Summary

In unit testing, gomock provides us with great convenience. Able to mock out many dependencies

There are many usage methods and functions. You can mark it and read the official documents in detail, and your memory will be deeper

The above is the entire content of this article. I hope it will be helpful to everyone’s study and I hope you will support me a lot.

tination=../mock/male_mock.go -package=mock github.com/EDDYCJY/mockd/person Male

type Male interface {
Get(id int64) error
}

We pay attention to the go:generate statement, which can be divided into the following parts:

  1. Declaration //go:generate (be careful not to leave spaces)
  2. Use mockgen command
  3. Definition -destination
  4. Definition -package
  5. Define source, here is the package path of person
  6. Define interfaces, here is Male

Regenerate mock files

Go back to the root directory of mockd/ and execute the following command

 $ go generate ./...

Check mock/ again and find that it has been generated correctly. Isn’t it very convenient when using multiple files? 🤩

Summary

In unit testing, gomock provides us with great convenience. Able to mock out many dependencies

There are many usage methods and functions. You can mark it and read the official documents in detail, and your memory will be deeper

The above is the entire content of this article. I hope it will be helpful to everyone’s study and I hope you will support me a lot.

This article is from the internet and does not represent1024programmerPosition, please indicate the source when reprinting:https://www.1024programmer.com/784265

author: admin

Previous article
Next article

Leave a Reply

Your email address will not be published. Required fields are marked *

Contact Us

Contact us

181-3619-1160

Online consultation: QQ交谈

E-mail: [email protected]

Working hours: Monday to Friday, 9:00-17:30, holidays off

Follow wechat
Scan wechat and follow us

Scan wechat and follow us

Follow Weibo
Back to top
首页
微信
电话
搜索