jarcoal / httpmock

HTTP mocking for Golang
http://godoc.org/github.com/jarcoal/httpmock
MIT License
1.91k stars 102 forks source link

Unable to Mock Responses with Custom http.Client and Transport #154

Open battlecook opened 4 months ago

battlecook commented 4 months ago

Thank you for creating a good package. I am using a custom http client. If I write the test code as below, it is working good.


func createHttpClient() *http.Client {
    httpClient := &http.Client{}
    return httpClient
}

func doHttp(cli *http.Client, addr string) string {
    reqUrl, _ := url.Parse(addr)
    req := &http.Request{
        Method: http.MethodGet,
        URL:    reqUrl,
    }
    res, err := cli.Do(req)
    if err != nil {
        panic(err)
    }

    body, err := io.ReadAll(res.Body)
    if err != nil {
        panic(err)
    }

    return string(body)
}

func TestHttpMock(t *testing.T) {

    httpmock.Activate()
    defer httpmock.DeactivateAndReset()

    addr := "http://example/foo"
    httpmock.RegisterResponder(http.MethodGet, addr,
        func(req *http.Request) (*http.Response, error) {
            return httpmock.NewStringResponse(http.StatusOK, "bar"), nil
        },
    )

    cli := createHttpClient()

    ret := doHttp(cli, addr)

    assert.Equal(t, "bar", ret)
}

However, I confirmed that I cannot receive a mock response if I add the transport option to createHttpClient as shown below.

func createHttpClient() *http.Client {
    httpClient := &http.Client{
        Transport: &http.Transport{},
    }
    return httpClient
}

Is there a way to make it work even if I add the transport option?

maxatome commented 4 months ago

Hello,

httpmock works using its own implementation of http.RoundTripper (i.e. httpmock.MockTransport).

By default, when enabling it globally using httpmock.Activate, it replaces the http.Client.Transport field of http.DefaultClient with httpmock.DefaultTransport.

If you create your own http.Client instance with its own Transport field, then in your tests you must replace this Transport field value by an *httpmock.MockTransport instance produced by NewMockTransport function.

Note that in that case there is no need to call httpmock.Activate function anymore.

func TestHttpMock(t *testing.T) {
    addr := "http://example/foo"

    mockTransport := httpmock.NewMockTransport()
    mockTransport.RegisterResponder(http.MethodGet, addr,
        func(req *http.Request) (*http.Response, error) {
            return httpmock.NewStringResponse(http.StatusOK, "bar"), nil
        },
    )

    cli := createHttpClient()
    cli.Transport = mockTransport

    ret := doHttp(cli, addr)

    assert.Equal(t, "bar", ret)
}
sylnsr commented 3 months ago

Mmmm, unfortunately this is not viable for libs that assert http.DefaultTransport See: https://github.com/grafana/grafana-openapi-client-go/issues/94

maxatome commented 3 months ago

Asserting that http.DefaultTransport is a specific type is a design mistake. Modifying the pointed data, as it is the case in the code you reference, is even a bigger mistake as it impacts other users of http.DefaultTransport.

Such libraries should just provide a way to override the http.Client.Transport field.

emirot commented 1 month ago

Facing the same issue unfortunately, so I ended up using gock which provides that functionality. https://github.com/h2non/gock?tab=readme-ov-file#mocking-a-custom-httpclient-and-httproundtripper

maxatome commented 1 month ago

Hi @emirot,

I didn't understand what gock does that httpmock cannot do, following the link you gave.

If you're talking about gock.InterceptClient function, you can do the same using httpmock:

var client *http.Client
… // set client
client.Transport = httpmock.DefaultTransport
// or
mock := NewMockTransport()
client.Transport = mock

Note that an equivalent of gock.InterceptClient function can easily be added, as the block code above proves it.

maxatome commented 1 month ago

I forgot ActivateNonDefault that already does that :)

emirot commented 1 month ago

@maxatome thanks so much ActivateNonDefault is exactly what I need. I missed it

maxatome commented 1 month ago

@emirot feel free to submit a pr to enhance the readme or the godoc 😉