Skip to content
/ gomuti Public

Mocking DSL for golang that incorporates Gomega matchers.

License

Notifications You must be signed in to change notification settings

xeger/gomuti

Repository files navigation

Build Status Go Report Docs Gomuti

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.

How to use

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.

Mocking calls: Allow()

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)

Spying on mocks: HaveCall()

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

Stubbing calls

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.

RSpec DSL

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

Terse DSL

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.

How to get help

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

How to contribute

Fork the xeger/gomuti repository on GitHub; make your changes; open a pull request.

About

Mocking DSL for golang that incorporates Gomega matchers.

Resources

License

Code of conduct

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published