Unit testing using Gomock

  golang, mock, php, unit testing

Unit testing using Gomock

Original address:Unit testing using Gomock

Preface

In actual projects, when unit tests are required. But often found a lot of dependencies. This is the timeGomockIt’s time to show your talents.

Gomock is a mock framework of Go language, the official one.

Installation

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

1. first step: we will install gomock third-party library and mock code generation tool mockgen. The latter can greatly save our workload. Only need to know how to use it

2. Step 2: InputmockgenVerify that the code generation tool is installed correctly. If you cannot respond normally, please checkbinDoes the directory contain the binary file

usage

InmockgenIn the 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 Reflector. It is enabled by passing two non-flag parameters: the import path and a comma-separated list of interfaces

mockgen database/sql/driver Conn,Driver

In essence, there is no difference between the mock codes 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 be familiar with the overall test process.

step

  1. Think clearly about the overall logic
  2. Define the interface of the desired (simulated) dependency
  3. UsemockgenThe command generates a mock file for the interface of the desired mock.
  4. Write the logic of unit tests and use mock in the tests.
  5. Perform unit test verification

Directory

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

write

Interface method

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

package person

type Male interface {
    Get(id int64) error
}

Calling method

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

package user

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

type User 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 file

Go back tomockd/, execute the following command

$ mockgen -source=./person/male.go -destination=./mock/male_mock.go -package=mock 

After the execution is completed, it can be found thatmock/Male_mock.go file is added to the directory, which is the mock file. What is the use of the instructions in the order? As follows:

  • -source: Sets the interface file that needs mock.
  • -destination: set the place where the mock file is output, otherwise print it to the standard output.
  • -package: set the package name of the mock file, otherwisemock_Prefix with file name (for example, the package name in this article will be mock_person)

For more instructions, seeOfficial documents

输出的 mock 文件
// 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 case

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

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: returngomock.ControllerThat represents the top-level control in the mock ecosystem. The scope, life cycle and expected value of mock object are defined. In addition, it is safe in multiple goroutine.
  2. Mock.newmock: create a new mock instance
  3. Gomock.InOrder: declares that a given call should be made sequentially (it is a secondary encapsulation of gomock.After)
  4. Mockmale.expect (). get (id). return (nil): there are three steps here.EXPECT()Returns a setting that allows callersExpectAndReturn valueThe object of the.Get(id)Is to set the parameter and call the method in the mock instance.Return(nil)Is to set the previously called method parameters. In short, it is to set the parameter and call, and finally set the return value.
  5. New User (mock): create user instance, note hereMock object injected, so the actual in the followinguser.GetUserInfo(id)Call (in reference: id 1). It calls the mock method that we simulated in advance.
  6. Ctl.Finish (): To make expected value assertion of mock use cases, generally usedeferDelay execution to prevent us from forgetting this operation.

test

Go back tomockd/, execute the following command

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

Seeing this result, we are done! You can adjust it yourself.Return()To get different test results

Check the test

Test coverage

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

Can be set by-coverIdentifier to open the coverage statistics, display content iscoverage: 100.0%.

Visual interface

1. Generate profile File of Test Coverage Rate

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

2. Use profile File to Generate Visual Interface

$ go tool cover -html=cover.out

3. Check the visual interface and analyze the coverage

image

More

I. mock Methods Commonly Used

Calling method

  • Do (): Declares the action to run when matching
  • Call.DoAndReturn (): Declares the operation to run when matching the call and returns the return value of the function by simulation
  • Call.MaxTimes (): set the maximum number of calls to n
  • Call.MinTimes (): set the minimum number of calls to n.
  • Call.AnyTimes (): The number of allowed calls is 0 or more
  • Call.Times (): set the number of calls to n.

Parameter matching

  • Gomock.Any (): matches any value
  • Eq (): matches to the specified type value by reflection without manual setting
  • Nil (): returns nil

For more suggestions, seeOfficial documents

2. Generating Multiple mock Files

You might want to generate mock files one by one, wouldn’t you crash?

Of course, the authorities have provided a more convenient way for us to make use of.go:generateTo 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 read as follows:

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 are concerned aboutgo:generateThis statement can be divided into the following parts:

  1. statement//go:generate(Be careful not to leave spaces)
  2. UsemockgenCommand
  3. Definition-destination
  4. Definition-package
  5. Definitionsource, here is person’s package path
  6. Definitioninterfaces, here isMale

Regenerate mock file

Go back tomockd/, execute the following command

$ go generate ./...

reexaminationmock/Discovery has also been correctly generated. Is it convenient to have multiple files?

Summary

In the unit test phase, gomock has provided us with great convenience. Can mock off many dependencies

There are many ways and functions to use it. You can read the official documents carefully after mark lives, and your memory will be more profound.