Search code examples
gohttprequest

Why does this GoLang Mock HTTP responder return the wrong number of times it was called?


I'm writing test cases for my Go application that makes HTTP requests. To simulate a response from the remote host, I have created this class stringProducer

type stringProducer struct {
    strings   []string
    callCount int
}

func (s *stringProducer) GetNext() string {
    if s.callCount >= len(s.strings) {
        panic("ran out of responses")
    }
    s.callCount++
    fmt.Println("s.CallCount = ", s.callCount)
    return s.strings[s.callCount-1]
}

func mockHTTPResponder(producer stringProducer) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        w.Write([]byte(producer.GetNext()))
    })
}

Here is how I call it in my main function:

func main() {
    producer := stringProducer{
        strings: []string{"Hello World!"},
    }

    srv := httptest.NewServer(mockHTTPResponder(producer))
    if producer.callCount != 0 {
        panic("callCount is not 0")
    }

    var buf io.ReadWriter
    req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, fmt.Sprintf("%s/path/to/something", srv.URL), buf)

    newClient := http.Client{}

    newClient.Do(req)

    if producer.callCount != 1 {
        panic("callCount is not 1")
    }
}

In this code, when an HTTP request is made, it goes to the responder above which responds with some pre-specified text. It also causes the counter stringProducer.callCount to be incremented by 1.

From the program's output below, you can see that it prints a line showing that callCount was incremented to 1. However, when I check that same value, it is not 1. It is zero. Why? And how to fix that?

s.CallCount =  1
panic: callCount is not 1

goroutine 1 [running]:
main.main()
    /tmp/sandbox3935766212/prog.go:50 +0x118

Go Playground link here: https://play.golang.org/p/mkiJAfrMdCw


Solution

  • You pass by value stringProducer in mockHTTPResponder. When you do this you get a copy of the variable inside mockHTTPResponder. And all the following changes are made on that copy (original stringProducer are left unchanged):

    func mockHTTPResponder(producer stringProducer) http.Handler { // <- producer is a copy of the original variable
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            w.WriteHeader(http.StatusOK)
            w.Write([]byte(producer.GetNext()))  // <- s.callCount++ on the copy
        })
    }
    

    Pass a pointer inside mockHTTPResponder.