Search code examples
unit-testinggogo-testinggomock

How to use gomock to make a mocked function return different results on subsequent calls?


I am using gomock to mock an http function that is called within the function under test. While it works great for one call it seems to call the first function again when I want to return a different result on the second call (see code below). The function calls the first mocked function (in calls slice) n number of times, so my question is: How can I use gomock to make a mocked function return different results on subsequent calls? I used this stackoverflow post for inspiration.

This is what I have until now:

ctrl := gomock.NewController(t)
defer ctrl.Finish()

// Mock http client
mockHttp := mock_http.NewMockHttp(ctrl)

// set up order of mock calls
var calls []*gomock.Call
for i := 0; i < len(tc.mockHttpResps); i++ {
    // Note: tc.mockHttpResps[i] is a *http.Response{}
    fn := mockHttp.EXPECT().Post(gomock.Any(), gomock.Any(),gomock.Any()).Return(tc.mockHttpResps[i], tc.mockHttpErrs[i]).Times(1) 
    calls = append(calls, fn)
}
gomock.InOrder(
   calls...
)

But it produces an error along these lines:

Unexpected call to *mock_rpc.MockHttp.Post because [function] has already been called the max number of times

If I try to set the calls to .AnyTimes() then the error goes away but the first mocked function is still called multiple times. I then get other errors such as EOF as the http.Response body is read twice, which is to be expected.

Note: I have tried to do exactly as in the linked post, but it leads to the same error.


Solution

  • As JonathanHall pointed out there is no need to mock http clients.

    This is the revised and working code:

    cnt := 0
    // mock http server
    svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if tc.mockHttpErrs[cnt] != nil {
            w.WriteHeader(http.StatusBadRequest)
            fmt.Fprintln(w, tc.mockHttpErrs[cnt].Error())
            cnt++
            return
        }
        fmt.Fprintf(w, tc.mockHttpResps[cnt])
        cnt++
    }))
    defer svr.Close()
    
    // use svr.URL and svr.Client() to inject into whatever code needs it
    

    This also allows you to be more flexible regarding the responses without relying on gomock!