This article introduces the basics of Gomock:

  • Use the sample
  • Making the document
  • The source code comments

Use the sample

Gomock is a go simulation framework that integrates well with go’s built-in testing package and can be used in other contexts.

The installation

Go version < 1.16

GO111MODULE = on the go a get github.com/golang/mock/[email protected]Copy the code

Go version 1.16 +

Go install github.com/golang/mock/[email protected]Copy the code

The project structure

We prepare the following documents:

  • Dal/Person.go, which corresponds to the layer in a typical project that interacts with the database or calls downstream RPC services.
  • Service /group.go, corresponding to the service layer in general projects, is mainly used to process various business logic.
  • Service /group_test.go, unit test file

Fill the content

Dal/Person. go: Suppose we have a table that stores the person information and use the Get(int) method to Get the corresponding person.

package dal

type Person interface {
   Get(id int) string
}
Copy the code

Service /group.go: our business logic processing layer.

package service

import "gomockStudy/dal"

type Group struct {
   person dal.Person
}

func (g *Group) GetPerson(id int) string {
   return g.person.Get(id)
}
Copy the code

Generating mock files

Go back to the project root directory and execute the following command:

 $mockgen -source=./dal/person.go -destination=./mock_gen/person_mock.go
Copy the code

After executing, we can see that there is an additional file: mock_gen/person_mock.go.

The mock file is generated with the following parameters:

  • -source: specifies the interface file to mock.
  • -destination: Sets the mock file name to be generated. If this parameter is not set, it is printed to standard output.
  • -packge: specifies the registration of mock files. If this parameter is not set, it ismock_Prefix followed by file name.

We don’t have to worry about the details of the mock files generated, just how to use them.

The test case

service/group.go

package service import ( "github.com/golang/mock/gomock" mock_dal "gomockStudy/mock_gen" "testing" ) func TestGroup_GetPerson(t *testing.T) { ctl := gomock.NewController(t) mockPerson := mock_dal.NewMockPerson(ctl) mockPerson.EXPECT().Get(gomock.Any()).Return("person1").AnyTimes() group := Group{mockPerson} person := group.GetPerson(1) if person ! = "person1" { t.Errorf("group.GetPerson id = 1, result = %v", person) } person = group.GetPerson(2) if person ! = "person2" { t.Errorf("group.GetPerson id = 2, result = %v", person) } }Copy the code
  1. gomock.NewController: Controller represents the top-level control of the simulated ecosystem. It defines the mock objectScope, life cycleAnd theirexpect. The method of calling a Controller from multiple Goroutines is thread-safe. Each test should create a new Controller.
  2. mock_dal.NewMockPersonCreate a mock Person object.
  3. Line 13:
    1. EXPECT(): returns an object that the caller can setExpected return value.
    2. Get(): sets the input parameter and calls the methods in the mock object.gomock.Any()Matches any input parameter.
    3. Return(): Sets the return value.
    4. AnyTimes(): an infinite number of calls, that is, the result will be returned no matter how many times the call is made.

Note: If the * testing.t object has been passed to the Controller when go 1.14+, Finish() is not called automatically.

test

Go back to the project root directory and execute the following command:

$go test. /service -- FAIL: TestGroup_GetPerson (0.00s) group_test.go:24: Group. GetPerson ID = 2, result = person1 FAIL gomockStudy/service 0.560s FAILCopy the code

As you can see, a line is expected to return person2, but person1 is returned. Modify line 13 in the test file:

mockPerson.EXPECT().Get(1).Return("person1")
mockPerson.EXPECT().Get(2).Return("person2")
Copy the code

The test success result can be obtained:

$go test./service ok gomockStudy/service 0.689sCopy the code

So far, we’ve learned how to use mock objects to help us unit test.

Let’s take a look at gomock’s Github page.

Git homepage

Run mockgen

Mockgen has two execution modes: Source and Reflect

Source mode

Source mode is used by using the -source parameter to generate the corresponding mock interface from the source code. In this mode, it is possible to use the -imports and -aux_files parameters.

mockgen -source=foo.go [other options]
Copy the code

Reflect mode

Reflection mode generates a mock interface by building a program that uses reflection to parse the interface. It is enabled by passing two non-flag arguments: the import path and a comma-separated list of symbols.

You can use “.” to indicate packages at the current path.

mockgen database/sql/driver Conn,Driver

# Convenient for `go:generate`.
mockgen . Conn,Driver
Copy the code

Flags

The mockgen command is used to generate source code for mock classes if the Go source file contains the interface to mock. It supports the following flags:

  • -source: the file containing the interface to mock.
  • -destination: writes the generated source code to the file. If this parameter is not set, the code is printed as standard output.
  • -package: the package used to generate the source code of the mock class. If this parameter is not specified, the package name ismock_Connects to the package of the input file.
  • -imports: An explicit list of imports that should be used in the generated source code, specified as a comma-separated list of elements of the form foo=bar/baz, where bar/baz is the imported package and foo is the identifier used by the package in the generated source code.
  • -aux_files: list of additional files to be queried, such as embedded interfaces defined in different files. It is specified as a comma-separated list of elements of the form foo=bar/baz, where bar/baz.go is the source file and foo is the package name of the file used by the -source file.
  • -build_flags :(reflection mode only) flags are passed verbatim to the build.
  • -mock_names: a list of custom names of generated mock objects. Looks like a comma-separated list of key-value pairs:

The Repository = MockSensorRepository, Endpoint = MockSensorEndpoint. Where, Repository is the interface name and MockSensorRepository is the name associated with the generated impersonation. If one of the interfaces does not specify a custom name, the default naming convention is used.

  • – self_Package: indicates the complete package import path of the generated code. The purpose of this flag is to prevent import loops in generated code by trying to include its own package. This can happen if the mock’s package is set as one of its inputs (usually the main input) and the output is STdio, so mockGen cannot detect the final output package. Setting this flag tells Mockgen which import to exclude.
  • -copyright_file: the copyright file used to add the copyright header to the generated source code.
  • -debug_parser: prints only the results of the parser.
  • -exec_only :(reflection mode) if set, the reflection program will be executed.
  • -prog_only :(reflection mode) generates only reflection programs; Write it to stdout and exit.
  • -write_package_comment: if true, write package documentation comments (godoc). (Default true)

Build away,

The example in the first video is a little bit more detailed than this one, so I’m not going to do it here.

Failed to modify message

When the matcher reports a failure, it prints the received value (Got) and the expected value (Want).

Got: [3]
Want: is equal to 2
Expected call at user_test.go:33 doesn't match the argument at index 1.
Got: [0 1 1 2 3]
Want: is equal to 1
Copy the code

Modify the Want

The Want value comes from the String() method of the matcher. If the default output of the matcher does not meet your needs, it can be modified as follows:

gomock.WantFormatter(
    gomock.StringerFunc(func() string { return "is equal to fifteen" }),
    gomock.Eq(15),
)
Copy the code

After the modification, the printed information of gomock.Eq(15) changes from is equal to 15 to is equal to fifteen.

Modify the Got

The Got value comes from the object’s String() method, if it is available. In some cases, the output of an object is difficult to read (for example, []byte), so it would be helpful to test printing it differently. The following command changes the format of the global offset table:

gomock.GotFormatterAdapter(
    gomock.GotFormatterFunc(func(i interface{}) string {
        return fmt.Sprintf("%02d", i)
    }),
    gomock.Eq(15),
)
Copy the code

If the received value is 3, it will be printed as 03.

Debugging Errors

Reflection vendoring error

cannot find package "."
... github.com/golang/mock/mockgen/model
Copy the code

If you encounter this error when using reflection mode and vendoring dependencies, there are three solutions:

  1. Use the source mode
  2. Import air import: import _ “making/golang/mock/mockgen/model”
  3. add--build_flags=--mod=modParameters.

This error is due to a change in the default behavior of the go command in recent versions.

The source code comments

Gomock’s source code is annotated with some information.

Execution order

By default, expected calls are not forced to run in any particular order.

Call order dependencies can be enforced by using InOrder and or call.after ().

Call.after () calls can create more diversified Call order dependencies, but InOrder is generally more convenient.

After

firstCall := mockObj.EXPECT().SomeMethod(1, "first")
secondCall := mockObj.EXPECT().SomeMethod(2, "second").After(firstCall)
mockObj.EXPECT().SomeMethod(3, "third").After(secondCall)
Copy the code

InOrder

gomock.InOrder(
    mockObj.EXPECT().SomeMethod(1, "first"),
    mockObj.EXPECT().SomeMethod(2, "second"),
    mockObj.EXPECT().SomeMethod(3, "third"),
)
Copy the code