• Stars
    star
    558
  • Rank 76,753 (Top 2 %)
  • Language
    Go
  • License
    MIT License
  • Created over 7 years ago
  • Updated 12 days ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Powerful mock generation tool for Go programming language

logo GoDoc Go Report Card Release Awesome

Summary

Minimock generates mocks out of Go interface declarations.

The main features of minimock are:

  • It generates statically typed mocks and helpers. There's no need for type assertions when you use minimock.
  • It's fully integrated with the standard Go "testing" package.
  • It's ready for Go modules.
  • It supports generics.
  • It works well with table driven tests because you can set up mocks for several methods in one line of code using the builder pattern.
  • It can generate several mocks in one run.
  • It generates code that passes gometalinter checks.
  • It puts //go:generate instruction into the generated code, so all you need to do when the source interface is updated is to run the go generate ./... command from within the project's directory.
  • It makes sure that all mocked methods have been called during the test and keeps your test code clean and up to date.
  • It provides When and Then helpers to set up several expectations and results for any method.
  • It generates concurrent-safe mocks and mock invocation counters that you can use to manage mock behavior depending on the number of calls.
  • It can be used with the GoUnit tool which generates table-driven tests that make use of minimock.

Installation

If you use go modules please download the latest binary or install minimock from source:

go install github.com/gojuno/minimock/v3/cmd/minimock@latest

If you don't use go modules please find the latest v2.x binary here or install minimock using v2 branch

Usage

 minimock [-i source.interface] [-o output/dir/or/file.go] [-g]
  -g	don't put go:generate instruction into the generated code
  -h	show this help message
  -i string
    	comma-separated names of the interfaces to mock, i.e fmt.Stringer,io.Reader
    	use io.* notation to generate mocks for all interfaces in the "io" package (default "*")
  -o string
    	comma-separated destination file names or packages to put the generated mocks in,
    	by default the generated mock is placed in the source package directory
  -p string 
        comma-separated package names,
        by default the generated package names are taken from the destination directory names
  -s string
    	mock file suffix (default "_mock_test.go")

Let's say we have the following interface declaration in github.com/gojuno/minimock/tests package:

type Formatter interface {
	Format(string, ...interface{}) string
}

This will generate mocks for all interfaces defined in the "tests" package:

$ cd ~/go/src/github.com/gojuno/minimock/tests
$ minimock 

Here is how to generate a mock for the "Formatter" interface only:

$ cd ~/go/src/github.com/gojuno/minimock/tests
$ minimock -i Formatter 

Same using the relative package notation:

$ minimock -i ./tests.Formatter

Same using the full import path of the source package:

$ minimock -i github.com/gojuno/minimock/tests.Formatter -o ./tests/

All the examples above generate ./tests/formatter_mock_test.go file

Now it's time to use the generated mock. There are several ways it can be done.

Setting up a mock using the builder pattern and Expect/Return methods:

mc := minimock.NewController(t)
formatterMock := NewFormatterMock(mc).FormatMock.Expect("hello %s!", "world").Return("hello world!")

The builder pattern is convenient when you have more than one method to mock. Let's say we have an io.ReadCloser interface which has two methods: Read and Close

type ReadCloser interface {
	Read(p []byte) (n int, err error)
	Close() error
}

We can set up a mock using a simple one-liner:

mc := minimock.NewController(t)
readCloserMock := NewReadCloserMock(mc).ReadMock.Expect([]byte(1,2,3)).Return(3, nil).CloseMock.Return(nil)

But what if we don't want to check all arguments of the read method? Let's say we just want to check that the second element of the given slice "p" is 2. This is where "Inspect" helper comes into play:

mc := minimock.NewController(t)
readCloserMock := NewReadCloserMock(mc).ReadMock.Inspect(func(p []byte){
  assert.Equal(mc, 2, p[1])
}).Return(3, nil).CloseMock.Return(nil)

Setting up a mock using When/Then helpers:

mc := minimock.NewController(t)
formatterMock := NewFormatterMock(mc)
formatterMock.FormatMock.When("Hello %s!", "world").Then("Hello world!")
formatterMock.FormatMock.When("Hi %s!", "there").Then("Hi there!")

alternatively you can use the one-liner:

formatterMock = NewFormatterMock(mc).When("Hello %s!", "world").Then("Hello world!").When("Hi %s!", "there").Then("Hi there!")

Setting up a mock using the Set method:

mc := minimock.NewController(t)
formatterMock := NewFormatterMock(mc).FormatMock.Set(func(string, ...interface{}) string {
  return "minimock"
})

You can also use invocation counters in your mocks and tests:

mc := minimock.NewController(t)
formatterMock := NewFormatterMock(mc)
formatterMock.FormatMock.Set(func(string, ...interface{}) string {
  return fmt.Sprintf("minimock: %d", formatterMock.BeforeFormatCounter())
})

Mocking context

Sometimes context gets modified by the time the mocked method is being called. However, in most cases you don't really care about the exact value of the context argument. In such cases you can use special minimock.AnyContext variable, here are a couple of examples:

mc := minimock.NewController(t)
senderMock := NewSenderMock(mc).
  SendMock.
    When(minimock.AnyContext, "message1").Then(nil).
    When(minimock.AnyContext, "message2").Then(errors.New("invalid message"))

or using Expect:

mc := minimock.NewController(t)
senderMock := NewSenderMock(mc).
  SendMock.Expect(minimock.AnyContext, "message").Return(nil)

Make sure that your mocks are being used

Often we write tons of mocks to test our code but sometimes the tested code stops using mocked dependencies. You can easily identify this problem by using minimock.NewController instead of just *testing.T. Alternatively you can use mc.Wait helper if your're testing concurrent code. These helpers ensure that all your mocks and expectations have been used at least once during the test run.

func TestSomething(t *testing.T) {
  // it will mark this example test as failed because there are no calls
  // to formatterMock.Format() and readCloserMock.Read() below
  mc := minimock.NewController(t)

  formatterMock := NewFormatterMock(mc)
  formatterMock.FormatMock.Return("minimock")

  readCloserMock := NewReadCloserMock(mc)
  readCloserMock.ReadMock.Return(5, nil)
}

Testing concurrent code

Testing concurrent code is tough. Fortunately minimock.Controller provides you with the helper method that makes testing concurrent code easy. Here is how it works:

func TestSomething(t *testing.T) {
  mc := minimock.NewController(t)

  //Wait ensures that all mocked methods have been called within the given time span
  //if any of the mocked methods have not been called Wait marks the test as failed
  defer mc.Wait(time.Second)

  formatterMock := NewFormatterMock(mc)
  formatterMock.FormatMock.Return("minimock")

  //tested code can run the mocked method in a goroutine
  go formatterMock.Format("hello world!")
}

Using GoUnit with minimock

Writing test is not only mocking the dependencies. Often the test itself contains a lot of boilerplate code. You can generate test stubs using GoUnit tool which has a nice template that uses minimock.

Happy mocking!

More Repositories

1

composer

Reactive Android Instrumentation Test Runner. Archived. Marathon is recommended as an alternative (https://github.com/Malinskiy/marathon).
Kotlin
547
star
2

koptional

Minimalistic Optional type for Kotlin that tries to fit its null-safe type system as smooth as possible.
Kotlin
285
star
3

swarmer

Reactive tool to create and start multiple Android Emulators in parallel.
Kotlin
201
star
4

test_tasks

Juno Inc., Test Tasks
42
star
5

engineering

Juno Engineering Blog
33
star
6

jrg

Juno Reverse Geocoder
Gherkin
28
star
7

genval

Generates Validate() methods for structs by tags
Go
28
star
8

lostgis

PLpgSQL
25
star
9

commander

Set of reactive functions for cli tools like Swarmer and Composer.
Kotlin
20
star
10

geo-py

Set of algorithms and structures related to geodesy
Python
17
star
11

osrm-py

Python client for OSRM API
Python
16
star
12

tracegen

Simple tool to generate opentracing decorators
Go
15
star
13

hexgrid-py

Configurable hex grid on abstract surface
Python
14
star
14

make-profiler

Makefile profiling toolset
Python
9
star
15

metricsgen

Tool to generate metrics decorators for interfaces
Go
8
star
16

hexgridgeo-py

GEO wrapper for HexGrid
Python
7
star
17

go-zooz

Zooz API client for Go
Go
7
star
18

generator

Package that simplifies development of go source code generators
Go
6
star
19

junocfg

Template based config generator
Go
6
star
20

morton-py

Morton Pack/Unpack Library
Python
4
star
21

go.morton

Morton Pack/Unpack Library
Go
4
star
22

hexgrid-pgsql

Configurable hexgrid
PLpgSQL
4
star
23

hexgrid-java

HexGrid
Java
4
star
24

goose

Fork of the https://bitbucket.org/liamstask/goose.git without SQLite support
Go
3
star
25

TestingPresentableComponents

Swift
3
star
26

FraktalBand

Swift
2
star
27

FraktalSimplified

Swift
2
star
28

RetryIt

Swift
1
star
29

morton-java

Morton Pack/Unpack Library
Java
1
star
30

frontend-config

JavaScript
1
star
31

morton-js

Morton Pack/Unpack Library
JavaScript
1
star
32

svgen

Scanners and valuers generator for basic types aliases
Go
1
star
33

go-checkout

Checkout.com API client
Go
1
star
34

go.hexgrid

HexGrid
Go
1
star
35

MortonSwift

Swift
1
star