Search code examples
unit-testinggotestify

Mock interface method twice with different input and output using testify


How can I mock an interface method twice in golang test? For example:

type myCache interface{
    Get(key string, data interface{}) error
}

type service struct {
    cache myCache
}

func (s service) GetBookDetail() (BookDetail, error) {
    ...
    book := Book{}
    err := s.cache.Get("book", &book)
    if err != nil {
        return BookDetail{}, err
    }
    ...
    author := Author{}
    err := s.cache.Get("author", &author)
    if err != nil {
        return BookDetail{}, err
    }
    ...
}

When I test func GetBookDetail(), how can I mock Get(key string, data interface{}) error twice? I try to do this but its failed:

func TestGetBookDetail(t *testing.T) {
    ...

    mockCache.On("Get",
        mock.MatchedBy(func(key string) bool {
            return key == "book"
        }), mock.MatchedBy(func(data interface{}) bool {
            return data == &Book{}
        })).Return(nil)

    mockCache.On("Get",
        mock.MatchedBy(func(key string) bool {
            return key == "author"
        }), mock.MatchedBy(func(data interface{}) bool {
            return data == &Author{}
        })).Return(nil)
    ...
    out, err := mockService.GetBookDetail()
    ...
}

Got error in test something like this:

Diff:

0: PASS: (string=book) matched by func(string) bool

1: FAIL: (*Book=&{ }) not matched by func() bool [recovered]

panic:

Note: I use github.com/stretchr/testify


Solution

  • First, to answer your question: Yes, you can specify how many times you want to return one value vs another. You can do that by using Once(), Twice(), or Times(n) as follows:

    m.On("foo", ...).Return(...).Once()
    

    Also, at the end of the test, you should confirm that the methods were called correct number of times by doing m.AssertExpectations(t).

    Now, my suggestion: It seems like you are over-complicating your mocks. You only have to use mock.MatchedBy when you wish to check partial equality or do some processing before checking for equality. In your case, m.On("Get", "book", &Book{})... should work just fine.

    Also, since you have different "inputs" to the mocked function - you don't necessarily need to add Once() at the end. It becomes compulsory only when you want to return different values but the arguments remain the same. However, it is mostly a good practice to assert whether the function was called the expected number of times.