Gomuti is DSL for mocking Golang interfaces, inspired by Gomega and drawing upon Gomega matchers to dispatch mock method calls. With a Ginkgo-like DSL for programming mock behavior, Gomuti makes it easy to write beautiful, well-isolated unit tests.
Mocks can also be spies and stubs, enabling behavior-driven development and terse, easy-to-maintain mock setup.
Imagine you have an interface that you want to mock.
type Adder interface {
Add(l, r int64) int64
}
To properly mock this interface, you need to create a struct type that has the same methods. The struct also holds two Gomuti-related fields that keep state about the programmed behavior of the mock and the observed method calls:
import gtypes "github.com/xeger/gomuti/types"
type MockAdder struct {
Mock gtypes.Mock
Spy gtypes.Spy
}
func(m *MockAdder) Add(l, r int64) int64 {
m.Spy.Observe(l, r)
r := m.Mock.Call("Add", l, r)
return r[0].(int64)
}
In reality you would use Mongoose to generate a mock type and methods for every interface in your package, but a hand-coded mock is fine for example purposes.
To program behavior into your mock, use the DSL methods in the gomuti
package. Allow()
instructs your mock to expect a method call and
tells it what to return.
Imagine you're writing unit tests for the Multiplier
type
and you want to isolate yourself from bugs in Adder
.
import (
. "github.com/onsi/ginkgo"
. "github.com/xeger/gomuti"
)
Describe("multiplier", func() {
var subject *multiplier
var adder Adder
BeforeEach(func() {
adder = &MockAdder{}
m := &multiplier{Adder:a}
})
It("computes the product of two integers", func() {
Allow(adder).Call("Add").With(5,5).Return(10)
Allow(adder).Call("Add").With(10,5).Return(15)
result := subject.Multiply(3,5))
Expect(result).To(Equal(15))
})
})
The Allow()
DSL can use any Gomega matcher for method parameters and Gomuti
provides a few matchers of its own; together, these allow you to mock
sophisticated behavior. Imagine your adder has a new AddStuff()
feature
that adds arbitrarily-typed values.
Allow(adder).Call("AddStuff").With(AnythingOfType("bool"), Anything()).Return(true)
You can use the HaveCall()
Gomega matcher to spy on your mock, verifying the
number of calls actually made to your mock as well as the specific parameter
values.
integer := AnythingOfType("int64")
Expect(adder).To(HaveCall("Add").With(integer, integer).Times(2))
If you generate your mocks with Mongoose,
then they come with a boolean Stub
field; setting this field to true causes
all methods to return zero values unless a matching call has been programmed.
Gomuti has some method aliases that imitate RSpec's plain-English DSL.
Expect(adder).ToReceive("Add").With(42,Anything()).AndReturn(42)
adder.Add(42, 7)
Expect(adder).To(HaveReceived("Add").Once())
Gomuti's long-form DSL uses concise English words as method names.
There is also a short-form DSL built around the method gomuti.Â()
. To produce
the  character, type Alt+0194
on Windows keyboards or Shift+Option+M
on Mac keyboards
(as a mnemonic, think "Â allows my Mock the option of being called.")
Short-form equivalents are provided for ToReceive()
and other chained methods, and
a super-terse form of  delivers maximum brevity. If we also use Gomega's Ω method, our
tests get very terse indeed. (Some would say "unreadable," but beauty is in the eye of
the beholder.)
// Super terse DSL
Â(adder,"Add",5,5).Return(10)
// Moderately terse DSL with complex matcher.
big := BeNumerically(">",2**32-1)
Â(adder).Call("Add").With(big,Anything()).Panic("integer overflow")
Ω(subject.Multiply(2,5)).Should(Equal(10))
Ω(adder).Should(HaveCall("Add").Times(2))
Ω(func() {
subject.Multiply(2**32-1,1)
}).Should(Panic())
Long and short method calls are interchangeable; even when using the
long-form Allow()
, we recommended using Call()
instead of ToReceive()
because the word "receive" is usually associated with the channel-receive
operation.
Check the frequently-asked questions to see if your problem is common.
Make sure to check Gomuti's godocs for relevant information.
If you think Gomuti is missing a feature, check the roadmap to see if a similar feature is already planned.
If you still need help, open an Issue. Clearly explain your problem, steps to reproduce, and your ideal solution (if known).
Fork the xeger/gomuti
repository on GitHub; make your changes; open a pull request.