Unit-test is used to test for the atomic level of the system under test (SUT). A unit may be an individual function, method, procedure, module, or object.
The most important benefit of unit-test is validate each unit performs as expected. It also help find out bugs, or incorrect logic inside of unit. And well-coded unit-test can be a good document of code.
To do unit-test we must isolate the unit of code, to archive this, we have Mock, Stub, and Fake.
Mocking is a technique to create a mock
object to simulate the behaviors of real
objects, but with our controls.
Mock object are programmed to response expected results from the expected input,
and it will validate the input and output of function, it also asserts how many time
it has been called.
In unit-test, mocking is often using for the de-couple modules of the system,
so that we just needed to test the logic and workflow business of each one.
Stub are like Mock, but it doesn’t make any assertions, just return the response as it programmed.
Fake objects is a ‘lightweight’ version of a real module in the system. It has own implementation, and act the same way as the real one but cannot use in production. For example, to test a third party’s service API, we use Fake for testing it, not the real one.
gomock
GoMock is a mocking framework for the Go programming language. It integrates well with Go’s built-in testing package, but can be used in other contexts too.
To install latest release version of gomock
GO111MODULE=on go get github.com/golang/mock/mockgen@v1.4.4
After installing success we can use mockgen
to create mock for our code.
mockgen
will work in two mode: Source and Reflect
Let say we have example here:
package structure
type Stacker interface {
Append(interface{}) []interface{}
}
type Queuer interface {
Enque(interface{}) []interface{}
}
When we use the flag -source
that mean have using the source mode. And the -source
will specify the file containing interfaces to be mocked. That may be lead to some
proplems when we don’t want to mock some interface. To prevent that, we can use
reflect mode instead. And we can add more flags, then we can customize the output
as the way we want.
For example:
mockgen -source=structure.go
After run this command, the mocked will be printed to standard output (your console)
and as you can see here, two mock objects MockStacker
, MockQueuer
are created.
// Code generated by MockGen. DO NOT EDIT.
// Source: structure.go
// Package mock_structure is a generated GoMock package.
package mock_structure
import (
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)
// MockStacker is a mock of Stacker interface
type MockStacker struct {
ctrl *gomock.Controller
recorder *MockStackerMockRecorder
}
// MockStackerMockRecorder is the mock recorder for MockStacker
type MockStackerMockRecorder struct {
mock *MockStacker
}
// NewMockStacker creates a new mock instance
func NewMockStacker(ctrl *gomock.Controller) *MockStacker {
mock := &MockStacker{ctrl: ctrl}
mock.recorder = &MockStackerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockStacker) EXPECT() *MockStackerMockRecorder {
return m.recorder
}
// Append mocks base method
func (m *MockStacker) Append(arg0 interface{}) []interface{} {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Append", arg0)
ret0, _ := ret[0].([]interface{})
return ret0
}
// Append indicates an expected call of Append
func (mr *MockStackerMockRecorder) Append(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Append", reflect.TypeOf((*MockStacker)(nil).Append), arg0)
}
// MockQueuer is a mock of Queuer interface
type MockQueuer struct {
ctrl *gomock.Controller
recorder *MockQueuerMockRecorder
}
// MockQueuerMockRecorder is the mock recorder for MockQueuer
type MockQueuerMockRecorder struct {
mock *MockQueuer
}
// NewMockQueuer creates a new mock instance
func NewMockQueuer(ctrl *gomock.Controller) *MockQueuer {
mock := &MockQueuer{ctrl: ctrl}
mock.recorder = &MockQueuerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockQueuer) EXPECT() *MockQueuerMockRecorder {
return m.recorder
}
// Enque mocks base method
func (m *MockQueuer) Enque(arg0 interface{}) []interface{} {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Enque", arg0)
ret0, _ := ret[0].([]interface{})
return ret0
}
// Enque indicates an expected call of Enque
func (mr *MockQueuerMockRecorder) Enque(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Enque", reflect.TypeOf((*MockQueuer)(nil).Enque), arg0)
}
Reflect mode will created mock for only listed name of interface in package. This mode required only two non-flag arguments: an import path, and a comma-separated list of interface name.
For example:
mockgen . Queuer, Stacker # is "." is referenced to the current path's package, this is good for using with go generate
# Or mockgen github.com/kurojs/gomock Queuer,Stacker
These will generate the same response as the first example.
mockgen
The mockgen
support for these flag, which are usefully to generate Mock for given class
-source: A file containing interfaces to be mocked. Cannot use with Reflect mode.
-destination: A file to which to write the resulting source code. If you don't set this, the code is printed to standard output.
High recommented given full package path here. Example: -destination=github.com/kurojs/gomock/mock_something.go
-package: The package to use for the resulting mock class source code. If you don't set this, the package name is mock_ concatenated with the package of the input file.
-imports: A list of explicit imports that should be used in the resulting source code, specified as a comma-separated list of elements of the form foo=bar/baz, where bar/baz is the package being imported and foo is the identifier to use for the package in the generated source code.
-aux_files: A list of additional files that should be consulted to resolve e.g. embedded interfaces defined in a different file. This is specified as a comma-separated list of elements of the form foo=bar/baz.go, where bar/baz.go is the source file and foo is the package name of that file used by the -source file.
-build_flags: (reflect mode only) Flags passed verbatim to go build.
-mock_names: A list of custom names for generated mocks. This is specified as a comma-separated list of elements of the form Repository=MockSensorRepository,Endpoint=MockSensorEndpoint, where Repository is the interface name and MockSensorRepository is the desired mock name (mock factory method and mock recorder will be named after the mock). If one of the interfaces has no custom name specified, then default naming convention will be used.
-self_package: The full package import path for the generated code. The purpose of this flag is to prevent import cycles in the generated code by trying to include its own package. This can happen if the mock's package is set to one of its inputs (usually the main one) and the output is stdio so mockgen cannot detect the final output package. Setting this flag will then tell mockgen which import to exclude.
-copyright_file: Copyright file used to add copyright header to the resulting source code.
mock := mock.NewMockStacker(ctrl)
// Assert mock are called 3 times
for i := 1; i <= 3; i++ {
mock.EXPECT().Append(gomock.Eq(i))
}
// And return 3
mock.EXPECT().Pop().Return(3)
mock := mock.NewMockStacker(ctrl)
// No assertion, just return the expected data
mock.EXPECT().Append(gomock.Any()).AnyTimes()
mock.EXPECT().Pop().DoAndReturn(func() interface{} {
return 3
})
You can find the example here: https://github.com/kurojs/gomock_demo