Extending GoMock With Matchers

Introduction

As our team continues to deploy more software written in Go, we’re refining our list of commonly used libraries and our own tooling around them. In particular, we’re pretty happy with GoMock and MockGen. There doesn’t seem to be many examples for this mocking framework in the wild, so I thought I’d share some of our simple extensions of it.

An Example

We have a Redis library that builds on Redigo. To illustrate an extension of the library’s Conn, consider the following code.

package mockdemo

import (
    "fmt"
    "time"
    "github.com/garyburd/redigo/redis"
)

type NuConn struct {
    redis.Conn
}

func (c *NuConn) SetTimestamp() error {
    _, err := c.Do("SET", "timestamp", fmt.Sprintf("Set at: %d", int32(time.Now().Unix())))
    return err
}

Pretty self explanatory. We have our custom type, which embeds Conn and has a method defined with it as the receiver.

Now since Conn is an interface, we can use MockGen to mock it for testing purposes. So assuming a standard GOPATH, we could do this with:

$ mockgen -source ~/go/src/github.com/garyburd/redigo/redis/redis.go -destination redis_mock.go

The generated mocks are placed in a package with the original name suffixed with mock_. I edited this file so they’re in mockdemo for convenience.

Incidentally, one of the things I really like about Go is the way interfaces are implicitly satisfied. This allows great testing flexibility. If a type in an upstream library does not have an interface defined, I can define my own and if the type satisfies it, I am free to use my interface everywhere I would otherwise use the type. In turn, I can mock my own interface - very useful.

This is how we might use our mock in the simplest possible test.

package mockdemo

import (
    "testing"
    "code.google.com/p/gomock/gomock"
)

func TestSetTimestampCallsDo(t *testing.T) {
    ctrl := gomock.NewController(t)
    defer ctrl.Finish()

    mc := NewMockConn(ctrl)
    c := NuConn{mc}

    mc.EXPECT().Do("SET", "timestamp", gomock.Any()).Return(interface{}([]byte("OK")), nil)

    if err := c.SetTimestamp(); err != nil {
        t.Fatal(err)
    }
}

Matchers

Notice the use of Any(). This is one of the Matchers supplied by GoMock. The others out of the box are Eq, Nil, and Not. If we take a look at the code, we see that Matcher is an interface. Sweet, we can define our own. Let’s try one out.

package mockdemo

import (
    "strings"
    "testing"
    "code.google.com/p/gomock/gomock"
)

type hasSubstr struct {
    values []string
}

func (m hasSubstr) Matches(arg interface{}) bool {
    sarg := arg.(string)
    for _, s := range m.values {
        if !strings.Contains(sarg, s) {
            return false
        }
    }
    return true
}

// Not used here, but satisfies the Matcher interface.
func (m hasSubstr) String() string {
    return strings.Join(m.values, ", ")
}

func HasSubstr(values ...string) gomock.Matcher {
    return hasSubstr{values: values}
}

func TestSetTimestampCallsDo(t *testing.T) {
    ctrl := gomock.NewController(t)
    defer ctrl.Finish()

    mc := NewMockConn(ctrl)
    c := NuConn{mc}

    mc.EXPECT().Do("SET", "timestamp", HasSubstr("Set", "at")).Return(interface{}([]byte("OK")), nil)

    if err := c.SetTimestamp(); err != nil {
        t.Fatal(err)
    }
}

Now we have matcher that can accept an arbitrary number of strings and check that they are substrings of the input - our test is more specific.

It’s easy to see that the possibilities are endless. We have others such as OneOf, Between and so forth, allowing us to test our assumptions more ruthlessly.

Happy hacking.