Simple and fast mock generator for golang
Mocking interfaces for unit tests is a common task in go which can be done manually or using a generator which would automates the process of repeatably writing structs
which fullfil the interfaces for testing.
There are at least two other popular mock generators that exist for go right now. The first is mockgen which is part of the mock module which was maintained by the golang team, but recently moved to a uber as new maintainer. The other is mockery which uses the testify mock interfaces.
So why "yet another mock generator", or in other words "what does smock offer that mockgen or mockery doesn't?"
The intention of smock is to simplify the process of manually mocking interfaces. The tool focused on the following features:
- Can be used as a library of a project or as a standalone tool using
go generate
- No boilerplate code such as
gomock.Controller
required to use the mock objects - Keep type information of input and return parameter when using the test doubles
- Clear and intuitive interface for mocked objects. No unnecessary info provided. For example there is no
Return
expression for mocked functions that do not return a value. - Fast parsing and generation
- No complex builtin assertion capabilities. Though, allow them to be added if needed for specific tests
Install latest version:
go install github.com/becheran/smock@latest
Annotate interface
which shall be mocked:
//go:generate smock
type MockMeIfYouCan interface {
Foo(bar int, baz string) (res int, err error)
}
Run the go generate command in the module root directory next to the go.mod
file to generate mocks for all annotated interfaces.
Using smock as an installed tool which is the same for all other mocking frameworks has the drawback that mock generation will fail if the tool is not installed on a developer PC as a prerequisite.
Instead of using smock as a cli tool it is also possible to add smock as a library dependency to a project and still be able to run it via go generate
.
Add smock as a dependency to your project:
go get github.com/becheran/smock
Create a new main method which will be used to generate mocks. A recommendation is to put it in the internal
directory to not expose it to the outside. For example internal/cmd/smock/main.go
. Add the go:generate
header to allow this method to be run from the go generate
command. See the documentation for how the mock generation can be configured:
package main
import "github.com/becheran/smock/smock"
//go:generate go run ./
func main() {
smock.GenerateMocks()
}
Once smock is setup the mock objects can be generated from the module root path:
go generate ./...
All generated mocks appear in the directory mocks
next to the corresponding module root path. The import name for the generated mocks will be <PackageNameOfInterface>_mock
.
A good idea might be to ignore all generated mocks. This can be achieved for example by adding the following line to your .gitignore
file:
*/**/*_mock
The mocked interface can be used in unit tests. They have an additional WHEN
function to set behaviors for each exposed function of the interface. The mock can either Do
something or Return
fixed values when a function is called.
The mocks can act like all types of mock objects described by martin fowler.
Directly pass mock to consumer:
func TestMockMeIfYouCan(t *testing.T) {
Consumer(foo_mock.NewMockMockMeIfYouCan(t))
}
Return fixed answers:
func TestMockMeIfYouCan(t *testing.T) {
mock := foo_mock.NewMockMockMeIfYouCan(t)
mock.WHEN().Foo().Return(42, nil)
Consumer(mock)
}
Assert arguments when being called:
func TestMockMeIfYouCan(t *testing.T) {
mock := gomod_test_mock.NewMockMockMeIfYouCan(t)
mock.WHEN().Foo().Expect(match.Eq(42), nil, match.Not(match.Eq("invalid")))
Consumer(mock)
}
Do and return arbitrary stuff when being called:
func TestMockMeIfYouCan(t *testing.T) {
mock := gomod_test_mock.NewMockMockMeIfYouCan(t)
ctr := 0
mock.WHEN().Foo().Do(func(bar int, baz string) (res int, err error) {
ctr++
return ctr, nil
}).Times(2)
Consumer(mock)
}