If we have some Go code using http.Client we might want to unit test it.

To do so we can create a new custom interface:

type Client interface {
	Do(req *http.Request) (*http.Response, error)
}

Having this new interface we can easily prepare a mock, in this example I’ll use testify:

type HTTPClientMock struct {
	mock.Mock
}

Then we declare the method we want to mock, in our case Do:

func (m *HTTPClientMock) Do(req *http.Request) (*http.Response, error) {
	args := m.Called(req)
	return args[0].(*http.Response), args.Error(1)
}

Then in our test we just have to create a new instance of that mock:

clientMock := new(HTTPClientMock)

We can use that mock to inject it to any service that requires http.Client, and then with the following code we can set the expectation we want:

m.
  On("Do", mock.MatchedBy(func(receivedReq *http.Request) bool {
    assert.EqualValues(t, req.Method, receivedReq.Method, "invalid method")
    assert.EqualValues(t, req.URL, receivedReq.URL, "invalid URL")
    assert.EqualValues(t, req.Header, receivedReq.Header, "invalid header")

    if "" == requestBody {
      // if body is empty
      assert.EqualValues(t, req.Body, receivedReq.Body, "invalid body")
    } else {
      // if we got some content
      var err error

      buf1 := new(bytes.Buffer)
      _, err = buf1.ReadFrom(req.Body)
      assert.NoError(t, err)

      buf2 := new(bytes.Buffer)
      _, err = buf2.ReadFrom(receivedReq.Body)
      assert.NoError(t, err)

      assert.JSONEq(t, buf1.String(), buf2.String(), "invalid JSON body")
    }

    return true
  })).
  Once().
  Return(res, nil)