2

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

Community
  • 1
  • 1
dev-x
  • 897
  • 3
  • 15
  • 30
  • 2
    Do not mock but use fakes or stubs as test doubles. This is much simpler, provides better testing and is more idiomatic. – Volker Jun 03 '20 at 05:47
  • 2
    https://stackoverflow.com/questions/47643192/how-to-mock-functions-in-golang/47643967#47643967 -- you can't, not even once. – mkopriva Jun 03 '20 at 06:02
  • Does this answer your question? [How to mock functions in golang](https://stackoverflow.com/questions/47643192/how-to-mock-functions-in-golang) – marco.m Jun 03 '20 at 06:38
  • 1
    Can you provide more details on how / what failed? Did it panic? Did you get an error? Did the test pass when you expect it to fail? Or did the test fail when you expect it to pass? What does the panic / error message say? – mkopriva Jun 03 '20 at 08:19
  • @dev-x - write less abstract code. How do you inject `interface` in `A()` method? – kozmo Jun 03 '20 at 08:43
  • I have updated my question – dev-x Jun 03 '20 at 09:29
  • @dev-x Your problem has nothing to do with how many times you want to test a method call. The problem is that you're checking for equality of two pointers that point to different memory locations. Regardless of the data pointed-to, two different addresses are *not* equal. https://play.golang.com/p/IdP3-W1al4Y (`data == &Author{}` is your problem). – mkopriva Jun 03 '20 at 09:50
  • @dev-x You can use `reflect.DeepEqual` to do a more complete comparison of the values pointed to. https://play.golang.com/p/i9pMmZj7lbA. Keep in mind that, DeepEqual is not all-knowing and it may return false negatives in some cases, if I remember correctly. – mkopriva Jun 03 '20 at 09:52
  • thank you @mkopriva, but is there any way to mock `Get` method? – dev-x Jun 03 '20 at 10:21
  • @dev-x Yes, in Go it is possible to mock any interface method, including `myCache.Get`. – mkopriva Jun 03 '20 at 10:34
  • instead of using `MatchedBy`, what is the better way to mock `myCache.Get`? – dev-x Jun 03 '20 at 12:23
  • @dev-x I don't know as I've never used `testify` nor `testify/mock`, however looking at its documentation, shouldn't it be enough to just pass the argument values directly, without the special matcher function? i.e. is something like this `mockCache.On("Get", "book", &Book{})` not working for you? – mkopriva Jun 03 '20 at 12:50

1 Answers1

6

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.

  • Thank you @Tarun Khandelwal. You're right, I don't need `mock.MatchedBy` and `Once()` at the end of mock function. I just write mock function twice with different input and output arguments. It works. – dev-x Jun 06 '20 at 16:45
  • @dev-x please write the solution that worked as an edit to your OP – Oliver Williams Jul 27 '21 at 08:05
  • `However, it is mostly a good practice to assert whether the function was called the expected number of times.` Why is that? I see it mentioned, and it is done by default when using mockery constructor. I thought of mocks as mocking what would happen if they were called. Checking the nr of times they were called seems like it only creates more coupling. – Marko Aug 22 '23 at 08:47