How to mock http endpoints in Golang with custom ports

So I was updating the testing suite in one of Tribe's microservices. This one in particular deals with sending the postbacks to the right endpoints.

If you search for testing http requests, chances are you will almost always stumble upon an example like:

import (  
    "log"
    "net/http"
    "net/http/httptest"
)
 ts := httptest.NewServer(http.HandlerFunc(func(w ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintln(w, "Hello, client")
    }))
    defer ts.Close()

res, err := http.Get(ts.URL)  
    if err != nil {
        log.Fatal(err)
    }    

//... other testing logic with the response object

Which is fine for most implementations, in which you have control of the port you want to make the request to. httptest.Server is linked to a different port each time, but you can find out which port by accessing its attribute ts.URL.

The problem I had is that the logic part of this microservice was not implemented with this concern in mind, and the whole logic of the microservice was jammed in a function. I could have modified it (and I probably should), but given that other people are working on that project, I did not want to modify it by myself.

This meant I had to find a way to modify the attribute ts.URL after creation. Go's httptest has the function NewUnstartedServer, which as per the documentation:

 NewUnstartedServer returns a new Server but doesn't start it.

After changing its configuration, the caller should call Start or StartTLS.

The caller should call Close when finished, to shut it down.

I thought, that is exactly what I need! , so I implemented the version:

ts := httptest.NewUnstartedServerhttp.HandlerFunc(func(w ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {  
        fmt.Fprintln(w, "Hello, client")
    }))
    ts.URL = "http://127.0.0.1:MY_DESIRED_PORT"
    ts.Start()
    defer ts.Close()

Interestingly, this implementation panics. By looking at the source code I found the reason:

// From https://golang.org/src/net/http/httptest/server.go?s=2374:2427#L93
// Start starts a server from NewUnstartedServer.

func (s *Server) Start() {

    if s.URL != "" {
        panic("Server already started") //--> this
    }

    s.URL = "http://" + s.Listener.Addr().String()
    s.wrap()
    s.goServe()
    if *serve != "" {
        fmt.Fprintln(os.Stderr, "httptest: serving on", s.URL)
        select {}
    }
}

I don't really know the reason for this implementation, but it was obvious that this approach would not work for me. I feel like the implementation would benefit of making ts.URL a private attribute and expose a getter ts.GetURL() method for clarity.

At the end, the implementation that I came up with (and I am sure there are other ones much better) is as follows:

import (  
    "net"
    "net/http"
    "net/http/httptest"
)
CUSTOM_URL := "127.0.0.1:MY_DESIRED_PORT"  
ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {  
        fmt.Fprintln(w, "Hello, client")
    }))
    l, _ := net.Listen("tcp", CUSTOM_URL)
    ts.Listener = l
    ts.Start()
    defer ts.Close()

res, err := http.Get(CUSTOM_URL)  
    if err != nil {
        log.Fatal(err)
    } 

Hopefully someone will find this helpful. Bye!

Show Comments