moka

package module
v0.0.0-...-e64c0ff Latest Latest
Warning

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

Go to latest
Published: Nov 5, 2017 License: Apache-2.0 Imports: 4 Imported by: 0

README


Moka

A Go mocking framework.
GoDOc TravisCI Go Report Card

Moka is a mocking framework for the Go programming language. Moka works very well with the Ginkgo testing framework, but can be easily used with any other testing framework, including the testing package from the standard library.

Getting Moka

go get github.com/gcapizzi/moka

Setting Up Moka

Ginkgo

Moka is designed to play well with Ginkgo. All you'll need to do is:

  • import the moka package;
  • register Ginkgo's Fail function as Moka's double fail handler using RegisterDoublesFailHandler.

Here's an example:

package game_test

import (
	. "github.com/gcapizzi/moka"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	"testing"
)

func TestGame(t *testing.T) {
	RegisterFailHandler(Fail)
	RegisterDoublesFailHandler(Fail)
	RunSpecs(t, "Game Suite")
}

testing and other frameworks

Support for the testing package hasn't been added yet, but this doesn't mean you can't use Moka with it.

Here is the type for the Moka doubles fail handler:

type FailHandler func(message string, callerSkip ...int)

This type is modelled to match Ginkgo's Fail function. To use Moka with the testing package, just provide a doubles fail handler that makes the test fail!

Here's an example:

package game

import (
	. "github.com/gcapizzi/moka"

	"testing"
)

func TestGame(t *testing.T) {
	RegisterDoublesFailHandler(func(message string, callerSkip ...int) {
		t.Fatal(message)
	})

	// use Moka here
}

Getting Started: Building Your First Double

A test double is an object that stands in for another object in your system during tests. The first step to use Moka is to declare a double type. The type will have to:

  • implement the same interface as the replaced object: this means that only objects used through interfaces can be replaced by Moka doubles;
  • embed the moka.Double type;
  • delegate any method that you'll need to stub/mock to the embedded moka.Double instance, using the Call method.

Let's build a double type for a Die interface:

package dice

import (
	. "github.com/gcapizzi/moka"
)

type Die interface {
	Roll(times int) []int
}

type DieDouble struct {
	Double
}

func NewDieDouble() DieDouble {
	return DieDouble{Double: NewStrictDouble()}
}

func (d DieDouble) Roll(times int) []int {
	returnValues, _ := d.Call("Roll", times)
	returnedRolls, _ := returnValues[0].([]int)
	return returnedRolls
}

Some notes:

  • The Double instance we are embedding is of type StrictDouble: strict doubles will fail the test if they receive a call on a method that wasn't previously allowed or expected.
  • If the Call invocation fails, it will both return an error as its second return value, and invoke the fail handler. Since we know the fail handler will immediately stop the execution of the test, we don't need to check for the error.
  • This style of type assertions allow us to have nil return values.

Allowing interactions

Now that our double type is ready, let's use it in our tests! We will test a Game type, which looks like this:

package game

type Game struct {
	die Die
}

func NewGame(die Die) Game {
	return Game{die: die}
}

func (g Game) Score() int {
	rolls := g.die.Roll(3)
	return rolls[0] + rolls[1] + rolls[2]
}

Here is the test:

package game_test

import (
	. "github.com/gcapizzi/moka/examples/game"

	. "github.com/gcapizzi/moka"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Game", func() {
	var die DieDouble
	var game Game

	BeforeEach(func() {
		die = NewDieDouble()
		game = NewGame(die)
	})

	Describe("Score", func() {
		It("returns the sum of three die rolls", func() {
			AllowDouble(die).To(ReceiveCallTo("Roll").With(3).AndReturn([]int{1, 2, 3}))

			Expect(game.Score()).To(Equal(6))
		})
	})
})

Typed doubles

You might be wondering: what happens if I allow a method call that would be impossible to perform, given the type of my double? Let's say we forgot how the Die interface was defined, and wrote a test like this:

Describe("Score", func() {
	It("returns the sum of three die rolls", func() {
		AllowDouble(die).To(ReceiveCallTo("Cast").With(3).AndReturn([]int{1, 2, 3}))

		Expect(game.Score()).To(Equal(9))
	})
})

The Die interface has no method called Cast, so our configured interaction will never happen!

To avoid this kind of problems, Moka provides typed doubles, which are associated with a type and will make sure that any configured interaction actually matches the type.

To instantiate a typed double, use the NewStrictDoubleWithTypeOf constructor:

func NewDieDouble() DieDouble {
	return DieDouble{Double: NewStrictDoubleWithTypeOf(DieDouble{})}
}

If run against a typed double, the previous test would fail with a message like this:

Invalid interaction: type 'DieDouble' has no method 'Cast'

Expecting interactions

Sometimes allowing a method call is not enough. Some methods have side effects, and we need to make sure they have been invoked in order to be confident that our code is working.

For example, let's assume we added a Logger collaborator to our Game type:

package game

import "fmt"

type Logger interface {
	Log(message string)
}

type StdoutLogger struct{}

func (l StdoutLogger) Log(message string) {
	fmt.Println(message)
}

We'll start, as usual, by building our double type:

type LoggerDouble struct {
	Double
}

func NewLoggerDouble() LoggerDouble {
	return LoggerDouble{Double: NewStrictDoubleWithTypeOf(LoggerDouble{})}
}

func (d LoggerDouble) Log(message string) {
	d.Call("Log", message)
}

We can now add the Logger collaborator to Game, and test their interaction:

Describe("Score", func() {
	It("returns the sum of three die rolls", func() {
		AllowDouble(die).To(ReceiveCallTo("Roll").With(3).AndReturn([]int{1, 2, 3}))
		ExpectDouble(logger).To(ReceiveCallTo("Log").With("[1, 2, 3]"))

		Expect(game.Score()).To(Equal(6))

		VerifyCalls(logger)
	})
})

We use ExpectDouble to expect method calls on a double, and VerifyCalls to verify that the calls have actually been made.

Custom interaction behaviour

If you need to specify a custom behaviour for your double interactions, of need to perform assertions on the arguments, you can specify a body for the interaction using AndDo:

Describe("Score", func() {
	It("returns the sum of three die rolls", func() {
		AllowDouble(die).To(ReceiveCallTo("Roll").AndDo(func(times int) []int {
			Expect(times).To(BeNumerically(">", 0))

			rolls := []int{}
			for i := 0; i < times; i++ {
				rolls = append(rolls, i+1)
			}
			return rolls
		}))

		Expect(game.Score()).To(Equal(6))
	})
})

How does Moka compare to the other Go mocking frameworks?

There are a lot of mocking libraries for Go out there, so why build a new one? Compared to those libraries, Moka offers:

  • A very readable syntax, inspired by RSpec.
  • A model that naturally supports stubbing or mocking different method calls with different arguments and return values, without the need to enforce any order, which would add unnecessary coupling between your tests and your implementation.
  • A relatively straightforward way to declare double types, without the need for a generator. We still plan to introduce a generator to make things even easier.
  • Strict doubles that will make your test fail on any unexpected interaction, instead of loose doubles that will return zero values and lead to confusing failures.

On the other hand, Moka currently lacks:

  • Compile-time type safety: in order to offer such a flexible and readable syntax, Moka cannot use the Go type system to detect invalid stub/mock declarations at compile-time. When using typed doubles, Moka will instead detect those errors at runtime and fail the test.
  • Support for argument matchers: will be implemented soon.
  • Support for enforced order of interactions: will be implemented soon.

Gotchas

Variadic Methods

Moka treats variadic arguments are regular slice arguments. This means that:

  • You should not unpack (as in SomeFunction(args...)) the slice containing the variadic values when passing it to Call.
  • You should use slices in place of variadic arguments when allowing or expecting method calls.

For example, given a Calculator interface:

type Calculator interface {
	Add(numbers ...int) int
}

This is how you should delegate Add to Call in the double implementation:

func (d CalculatorDouble) Add(numbers ...int) int {
	returnValues, _ := d.Call("Add", numbers)

	// ...
}

And this is how you should allow a call to Add:

AllowDouble(calculator).To(ReceiveCallTo("Add").With([]int{1, 2, 3}).AndReturn(6))

Documentation

Overview

Package moka provides a mocking framework for the Go programming language. Moka works very well with the Ginkgo testing framework, but can be easily used with any other testing framework, including the testing package from the standard library.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func RegisterDoublesFailHandler

func RegisterDoublesFailHandler(failHandler FailHandler)

RegisterDoublesFailHandler registers a function as the global fail handler used by newly instantiated Moka doubles.

func VerifyCalls

func VerifyCalls(double Double)

VerifyCalls verifies that all expected interactions on the wrapper `Double` have actually happened.

Types

type AllowanceTarget

type AllowanceTarget struct {
	// contains filtered or unexported fields
}

AllowanceTarget wraps a Double to enable the configuration of allowed interactions on it.

func AllowDouble

func AllowDouble(double Double) AllowanceTarget

AllowDouble wraps a Double in an `AllowanceTarget`.

func (AllowanceTarget) To

func (t AllowanceTarget) To(interactionBuilder InteractionBuilder)

To configures the interaction built by the provided `InteractionBuilder` on the wrapped `Double`.

type ArgsInteractionBuilder

type ArgsInteractionBuilder struct {
	// contains filtered or unexported fields
}

ArgsInteractionBuilder allows to build interactions that are defined by a method name, a list of arguments and a list of return values

func (ArgsInteractionBuilder) AndReturn

func (b ArgsInteractionBuilder) AndReturn(returnValues ...interface{}) ArgsInteractionBuilder

AndReturn allows to specify the return value of the interaction.

type BodyInteractionBuilder

type BodyInteractionBuilder struct {
	// contains filtered or unexported fields
}

BodyInteractionBuilder allows to build interactions that are defined by a method name and a custom body

type Double

type Double interface {
	Call(methodName string, args ...interface{}) ([]interface{}, error)
	// contains filtered or unexported methods
}

Double is the interface implemented by all Moka double types.

type ExpectationTarget

type ExpectationTarget struct {
	// contains filtered or unexported fields
}

ExpectationTarget wraps a Double to enable the configuration of expected interactions on it.

func ExpectDouble

func ExpectDouble(double Double) ExpectationTarget

ExpectDouble wraps a Double in an `ExpectationTarget`.

func (ExpectationTarget) To

func (t ExpectationTarget) To(interactionBuilder InteractionBuilder)

To configures the interaction built by the provided `InteractionBuilder` on the wrapped `Double`.

type FailHandler

type FailHandler func(message string, callerSkip ...int)

FailHandler is the type required for Moka fail handler functions. It matches the type of the Ginkgo `Fail` function.

type InteractionBuilder

type InteractionBuilder interface {
	// contains filtered or unexported methods
}

InteractionBuilder provides a fluid interface to build interactions to configure on a `Double`

type MethodInteractionBuilder

type MethodInteractionBuilder struct {
	// contains filtered or unexported fields
}

MethodInteractionBuilder allows to build interactions that are specific to a method. It turns into more specific builders through the fluid interface methods.

func ReceiveCallTo

func ReceiveCallTo(methodName string) MethodInteractionBuilder

ReceiveCallTo allows to specify the method name of the interaction.

func (MethodInteractionBuilder) AndDo

func (b MethodInteractionBuilder) AndDo(body interface{}) BodyInteractionBuilder

AndDo allows to specify a custom body to be executed by the interaction.

func (MethodInteractionBuilder) AndReturn

func (b MethodInteractionBuilder) AndReturn(returnValues ...interface{}) ArgsInteractionBuilder

AndReturn allows to specify the return value of the interaction.

func (MethodInteractionBuilder) With

func (b MethodInteractionBuilder) With(args ...interface{}) ArgsInteractionBuilder

With allows to specify the expected arguments of the interaction.

type StrictDouble

type StrictDouble struct {
	// contains filtered or unexported fields
}

StrictDouble is a strict implementation of the Double interface. Any invocation of the `Call` method that won't match any of the configured interactions will trigger a test failure and return an error.

func NewStrictDouble

func NewStrictDouble() *StrictDouble

NewStrictDouble instantiates a new `StrictDouble`, using the global fail handler and no validation on the configured interactions.

func NewStrictDoubleWithTypeOf

func NewStrictDoubleWithTypeOf(value interface{}) *StrictDouble

NewStrictDoubleWithTypeOf instantiates a new `StrictDouble`, using the global fail handler and validating that any configured interaction matches the specified type.

func (*StrictDouble) Call

func (d *StrictDouble) Call(methodName string, args ...interface{}) ([]interface{}, error)

Call performs a method call on the double. If a matching interaction is found, its return values will be returned. If no configured interaction matches, an error will be returned.

Jump to

Keyboard shortcuts

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