7

For sentence

resp, err := client.Get(fmt.Sprintf("https://www.xxxxx/day?time=%s", time))

If I want to mock a response to this client.Get() in unit test, I should use httptest.server, but how can I bind the url (https://www.xxxxx/day?time=%s) to the url of httptest.server? so that when I call client.Get() it can return the response I set before. For some reason I cannot mock a client here.

Pineapple
  • 111
  • 1
  • 4
  • 1
    It would be helpful to post your code so we can see what you're trying to accomplish. – Dale Oct 26 '17 at 06:51

3 Answers3

4

You don't, usually. You take the base URL from the server and give it to the client:

package main

import (
    "fmt"
    "net/http"
    "net/http/httptest"
    "testing"
    "time"
)

func TestClient(t *testing.T) {
    server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            // Verify request, send mock response, etc.
    }))

    defer server.Close()

    var client *http.Client
    var time time.Time

    baseURL := server.URL // Something like "http://127.0.0.1:53791"
    resp, err := client.Get(fmt.Sprintf(baseURL+"/day?time=%s", time))
    if err != nil {
            t.Fatal(err)
    }

    // Verify response body if applicable

    resp.Body.Close()
}
Peter
  • 29,454
  • 5
  • 48
  • 60
4

Like this

func NewTestServerWithURL(URL string, handler http.Handler) (*httptest.Server, error) {
    ts := httptest.NewUnstartedServer(handler)
    if URL != "" {
        l, err := net.Listen("tcp", URL)
        if err != nil {
            return nil, err
        }
        ts.Listener.Close()
        ts.Listener = l
    }
    ts.Start()
    return ts, nil
}
Chris Redford
  • 16,982
  • 21
  • 89
  • 109
  • 1
    Thanks a lot! This just saved my day - I couldn't figure out how to create a test server on a custom port and yours is the answer! – Andrey May 26 '23 at 15:11
0

The http.Client is a struct not an interface which makes mocking it difficult as you have seen. An alternative way of mocking it is passing in the external dependencies that a routine needs, so instead of directly using client.Get, you use clientGet - which is a function pointer that was handed into the routine.

From the unit test you can then create :

mockClientGet(c *http.client, url string) (resp *http.Response, err error) {
// add the test code to return what you want it to.
}

Then in your main code use:

resp, err := clientGet(client, fmt.Sprintf("https://www.xxxxx/day?time=%s", time))

When calling the procedure normally, use the function pointer to http.Client.Get, and for your test pass in a pointer to your mock. It's not ideal, but I've not seen a nicer way around mocking non-interface external calls - and given its an external dependency, injecting it from the outside is not a bad thing.

Andrew
  • 26,629
  • 5
  • 63
  • 86