maraino / go-mock

A mocking framework for the Go Programming Language.
MIT License
35 stars 12 forks source link

Question [Not a issue] #4

Closed prvn closed 8 years ago

prvn commented 8 years ago

Hello, I am not sure how to ask a question, so opening a issue.

I am trying to mock a chain of function calls and i am not really getting how to do it from the documentation. Hope you can help me.

I am mocking redis client ("gopkg.in/redis.v3")

All calls to this API looks something like below

redis.Client.ZAdd("test", data...).Result() redis.Client.ZRem("test", data...).Result()

Result() in ZAdd case belongs to struct *redis.IntCmd and Result() in ZRem case belongs to some other struct

How can I mock these API's ?

Appreciate your help

maraino commented 8 years ago

Hi @prvn,

The redis package is not designed to be mockable, if you want to mock it you will need to create your own mockable wrapper.

Here you have an example:

package mock

import (
    "errors"
    "testing"

    "gopkg.in/redis.v3"
)

type Client struct {
    base redis.Client
}

func (c *Client) ZAdd(key string, members ...redis.Z) IntCmdInterface {
    return c.base.ZAdd(key, members...)
}

func (c *Client) ZRem(key string, members ...string) IntCmdInterface {
    return c.base.ZRem(key, members...)
}

type ClientInterface interface {
    ZAdd(key string, members ...redis.Z) IntCmdInterface
    ZRem(key string, members ...string) IntCmdInterface
}

type ClientMock struct {
    Mock
}

func (m *ClientMock) ZAdd(key string, members ...redis.Z) IntCmdInterface {
    ret := m.Called(key, members)
    return ret.Get(0).(IntCmdInterface)
}

func (m *ClientMock) ZRem(key string, members ...string) IntCmdInterface {
    ret := m.Called(key, members)
    return ret.Get(0).(IntCmdInterface)
}

type IntCmdInterface interface {
    Result() (int64, error)
}

type IntCmdMock struct {
    Mock
}

func (m *IntCmdMock) Result() (int64, error) {
    ret := m.Called()
    return ret.Int64(0), ret.Error(1)
}

func TestRedis(t *testing.T) {
    client := ClientMock{}
    cmd1 := &IntCmdMock{}
    cmd2 := &IntCmdMock{}
    cmd3 := &IntCmdMock{}

    cmd1.When("Result").Return(int64(123), nil)
    cmd2.When("Result").Return(int64(456), nil)
    cmd3.When("Result").Return(int64(0), errors.New("an error"))

    client.When("ZAdd", "foo", Any).Return(cmd1)
    client.When("ZRem", "bar", Any).Return(cmd2)
    client.When("ZRem", "zar", Any).Return(cmd3)

    ret := client.ZAdd("foo", redis.Z{1.00, "member"})
    if i, err := ret.Result(); i != 123 || err != nil {
        t.Fatal("Result is not 123")
    }

    ret = client.ZRem("bar")
    if i, err := ret.Result(); i != 456 || err != nil {
        t.Fatal("Result is not 456")
    }

    ret = client.ZRem("zar")
    if i, err := ret.Result(); i != 0 || err == nil || err.Error() != "an error" {
        t.Fatal("error is not `an error`")
    }
}
prvn commented 8 years ago

That was super helpful! Thanks @maraino

maraino commented 8 years ago

@prvn And of course as you're doing your own wrapper you can simplify the mock if you do things like:

func (c *Client) ZAdd(key string, members ...redis.Z) (int64, error) {
    return c.base.ZAdd(key, members...).Result()
}
prvn commented 8 years ago

I was thinking about the same, so that I can avoid mocking all the Result() functions of each Cmd structs.