minimock

package module
v3.3.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 20, 2024 License: MIT Imports: 9 Imported by: 175

README

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!

Documentation

Overview

Package minimock is a command line tool that parses the input Go source file that contains an interface declaration and generates implementation of this interface that can be used as a mock.

Main features of minimock

1. It's integrated with the standard Go "testing" package

2. It supports variadic methods and embedded interfaces

3. It's very convenient to use generated mocks in table tests because it implements builder pattern to set up several mocks

4. It provides a useful Wait(time.Duration) helper to test concurrent code

5. It generates helpers to check if the mocked methods have been called and keeps your tests clean and up to date

6. It generates concurrent-safe mock execution counters that you can use in your mocks to implement sophisticated mocks behaviour

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

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

Here is how to generate the mock for this interface:

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

The result file ./tests/formatter_mock_test.go will contain the following code:

//FormatterMock implements github.com/gojuno/minimock/tests.Formatter
type FormatterMock struct {
	t minimock.Tester

	FormatFunc    func(p string, p1 ...interface{}) (r string)
	FormatCounter uint64
	FormatMock    mFormatterMockFormat
}

//NewFormatterMock returns a mock for github.com/gojuno/minimock/tests.Formatter
func NewFormatterMock(t minimock.Tester) *FormatterMock {
	m := &FormatterMock{t: t}

	if controller, ok := t.(minimock.MockController); ok {
		controller.RegisterMocker(m)
	}

	m.FormatMock = mFormatterMockFormat{mock: m}

	return m
}

type mFormatterMockFormat struct {
	mock             *FormatterMock
	mockExpectations *FormatterMockFormatParams
}

//FormatterMockFormatParams represents input parameters of the Formatter.Format
type FormatterMockFormatParams struct {
	p  string
	p1 []interface{}
}

//Expect sets up expected params for the Formatter.Format
func (m *mFormatterMockFormat) Expect(p string, p1 ...interface{}) *mFormatterMockFormat {
	m.mockExpectations = &FormatterMockFormatParams{p, p1}
	return m
}

//Return sets up a mock for Formatter.Format to return Return's arguments
func (m *mFormatterMockFormat) Return(r string) *FormatterMock {
	m.mock.FormatFunc = func(p string, p1 ...interface{}) string {
		return r
	}
	return m.mock
}

//Set uses given function f as a mock of Formatter.Format method
func (m *mFormatterMockFormat) Set(f func(p string, p1 ...interface{}) (r string)) *FormatterMock {
	m.mock.FormatFunc = f
	return m.mock
}

//Format implements github.com/gojuno/minimock/tests.Formatter interface
func (m *FormatterMock) Format(p string, p1 ...interface{}) (r string) {
	defer atomic.AddUint64(&m.FormatCounter, 1)

	if m.FormatMock.mockExpectations != nil {
		testify_assert.Equal(m.t, *m.FormatMock.mockExpectations, FormatterMockFormatParams{p, p1},
			"Formatter.Format got unexpected parameters")

		if m.FormatFunc == nil {
			m.t.Fatal("No results are set for the FormatterMock.Format")
			return
		}
	}

	if m.FormatFunc == nil {
		m.t.Fatal("Unexpected call to FormatterMock.Format")
		return
	}

	return m.FormatFunc(p, p1...)
}

//FormatMinimockCounter returns a count of Formatter.Format invocations
func (m *FormatterMock) FormatMinimockCounter() uint64 {
	return atomic.LoadUint64(&m.FormatCounter)
}

//MinimockFinish checks that all mocked methods of the interface have been called at least once
func (m *FormatterMock) MinimockFinish() {
	if m.FormatFunc != nil && atomic.LoadUint64(&m.FormatCounter) == 0 {
		m.t.Fatal("Expected call to FormatterMock.Format")
	}
}

//MinimockWait waits for all mocked methods to be called at least once
//this method is called by minimock.Controller
func (m *FormatterMock) MinimockWait(timeout time.Duration) {
	timeoutCh := time.After(timeout)
	for {
		ok := true
		ok = ok && (m.FormatFunc == nil || atomic.LoadUint64(&m.FormatCounter) > 0)

		if ok {
			return
		}

		select {
		case <-timeoutCh:

			if m.FormatFunc != nil && atomic.LoadUint64(&m.FormatCounter) == 0 {
				m.t.Error("Expected call to FormatterMock.Format")
			}

			m.t.Fatalf("Some mocks were not called on time: %s", timeout)
			return
		default:
			time.Sleep(time.Millisecond)
		}
	}
}

There are several ways to set up a mock

Setting up a mock using direct assignment:

formatterMock := NewFormatterMock(mc)
formatterMock.FormatFunc = func(string, ...interface{}) string {
	return "minimock"
}

Setting up a mock using builder pattern and Return method:

formatterMock := NewFormatterMock(mc).FormatMock.Expect("%s %d", "string", 1).Return("minimock")

Setting up a mock using builder and Set method:

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

Builder pattern is convenient when you have to mock more than one method of an interface. 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
	}

Then you can set up a mock using just one assignment:

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

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

formatterMock := NewFormatterMock(mc)
formatterMock.FormatFunc = func(string, ...interface{}) string {
	return fmt.Sprintf("minimock: %d", formatterMock.FormatMinimockCounter())
}

minimock.Controller

When you have to mock multiple dependencies in your test it's recommended to use minimock.Controller and its Finish or Wait methods. All you have to do is instantiate the Controller and pass it as an argument to the mocks' constructors:

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

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

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

	readCloserMock.Read([]byte{})
	formatterMock.Format()
}

Every mock is registered in the controller so by calling mc.Finish() you can verify that all the registered mocks have been called within your test.

Keep your tests clean

Sometimes we write tons of mocks for our tests but over time the tested code stops using mocked dependencies, however mocks are still present and being initialized in the test files. So while tested code can shrink, tests are only growing. To prevent this minimock provides Finish() method that verifies that all your mocks have been called at least once during the test run.

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

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

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

 		//this test will fail because there are no calls to formatterMock.Format() and readCloserMock.Read()
	}

Testing concurrent code

Testing concurrent code is tough. Fortunately minimock 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 given interval
	//if any of the mocked methods have not been called Wait marks test as failed
	defer mc.Wait(time.Second)

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

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

Minimock comman line args:

	$ minimock -h
  Usage of minimock:
    -f string
      	DEPRECATED: input file or import path of the package that contains interface declaration
    -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 an io package
    -o string
      	destination file name to place the generated mock or path to destination package when multiple interfaces are given
    -p string
      	DEPRECATED: destination package name
    -s string
      	output file name suffix which is added to file names when multiple interfaces are given (default "_mock_test.go")
    -t string
      	DEPRECATED: mock struct name (default <interface name>Mock)
    -withTests
      	parse *_test.go files in the source package

Index

Constants

View Source
const (
	// HeaderTemplate is used to generate package clause and go:generate instruction
	HeaderTemplate = `` /* 883-byte string literal not displayed */

	// BodyTemplate is used to generate mock body
	BodyTemplate = `` /* 15184-byte string literal not displayed */

)

Variables

View Source
var AnyContext = anyContext{}

Functions

func CamelToSnake

func CamelToSnake(s string) string

CamelToSnake transforms strings from CamelCase to snake_case

func Diff

func Diff(e, a interface{}) string

Diff returns unified diff of the textual representations of e and a

func Equal

func Equal(a, b interface{}) bool

Equal returns true if a equals b

Types

type Controller

type Controller struct {
	Tester
	sync.Mutex
	// contains filtered or unexported fields
}

Controller implements MockController interface and has to be used in your tests: mockController := minimock.NewController(t) defer mockController.Finish() stringerMock := NewStringerMock(mockController)

func NewController

func NewController(t Tester) *Controller

NewController returns an instance of Controller

func (*Controller) Finish deprecated

func (c *Controller) Finish()

Finish calls to MinimockFinish method for all registered mockers

Deprecated: Finish exists for historical compatibility and should not be used. Current implementation of controller registers Finish as Cleanup function for the testing.T instance passed to NewController.

func (*Controller) RegisterMocker

func (c *Controller) RegisterMocker(m Mocker)

RegisterMocker puts mocker to the list of controller mockers

func (*Controller) Wait

func (c *Controller) Wait(d time.Duration)

Wait calls to MinimockWait method for all registered mockers

type MockController

type MockController interface {
	Tester

	RegisterMocker(Mocker)
}

MockController can be passed to mocks generated by minimock

type Mocker

type Mocker interface {
	MinimockFinish()
	MinimockWait(time.Duration)
}

Mocker describes common interface for all mocks generated by minimock

type Tester

type Tester interface {
	Fatal(args ...interface{})
	Fatalf(format string, args ...interface{})
	Error(...interface{})
	Errorf(format string, args ...interface{})
	FailNow()
	Cleanup(f func())
}

Tester contains subset of the testing.T methods used by the generated code

Directories

Path Synopsis
cmd
internal
Package tests contains tests for minimock tool and demonstrates minimock features
Package tests contains tests for minimock tool and demonstrates minimock features

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL