Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
battlecook opened this issue May 9, 2024 · 8 comments
Open

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

battlecook opened this issue May 9, 2024 · 8 comments

Comments

@battlecook
Copy link
Contributor

battlecook commented May 9, 2024

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
Copy link
Collaborator

maxatome commented May 12, 2024

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
Copy link

sylnsr commented May 20, 2024

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

@maxatome
Copy link
Collaborator

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
Copy link

emirot commented Aug 8, 2024

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
Copy link
Collaborator

maxatome commented Aug 8, 2024

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
Copy link
Collaborator

maxatome commented Aug 8, 2024

I forgot ActivateNonDefault that already does that :)

@emirot
Copy link

emirot commented Aug 8, 2024

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

@maxatome
Copy link
Collaborator

maxatome commented Aug 8, 2024

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants