Skip to content

becheran/smock

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

85 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

smock

Pipeline Status Doc Go Report Card PRs Welcome License

Simple and fast mock generator for golang

Logo

Features

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

Setup

Standalone Tool

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.

Library Tool

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()
}

Generate Mocks

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

Use Mocked Objects

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.

Dummy

Directly pass mock to consumer:

func TestMockMeIfYouCan(t *testing.T) {
 Consumer(foo_mock.NewMockMockMeIfYouCan(t))
}

Stub

Return fixed answers:

func TestMockMeIfYouCan(t *testing.T) {
 mock := foo_mock.NewMockMockMeIfYouCan(t)
 mock.WHEN().Foo().Return(42, nil)
 Consumer(mock)
}

Spy

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)
}

Mock or Fake

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)
}