Hello there,
I’m confused about testing HTTP handlers. I’ve seen two approaches and I’d like to learn what are the pros and cons of each.
For instance, having the following custom server:
package main
import (
"context"
"fmt"
"io"
"log"
"net/http"
"time"
)
const maxTimeout = 5 * time.Second
var server = &http.Server{}
func main() {
server := setUpServer()
fmt.Println("Server running...")
err := server.ListenAndServe()
if err != nil {
log.Panic(err)
}
}
func setUpServer() *http.Server {
mux := http.NewServeMux()
mux.Handle("/slow", http.TimeoutHandler(http.HandlerFunc(slowHandler), maxTimeout, "timeout"))
server.Addr = ":4040"
server.Handler = mux
return server
}
func slowHandler(w http.ResponseWriter, r *http.Request) {
data := slowOperation(r.Context())
io.WriteString(w, data+"\n")
}
func slowOperation(ctx context.Context) string {
fmt.Println("Working on it...")
select {
case <-ctx.Done():
fmt.Println("Operation timed out.")
return ""
case <-time.After(10 * time.Second):
return "data"
}
}
A handler could be tested using the custom server like:
package main
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestServer(t *testing.T) {
t.Run("slowHandler should return 503 if request timeouts", func(t *testing.T) {
server := setUpServer()
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/slow", nil)
server.Handler.ServeHTTP(w, r) // Using the custom server
expected := http.StatusServiceUnavailable
got := w.Result().StatusCode
if got != expected {
t.Errorf("expected request to cause %v, got %v", expected, got)
}
})
}
But also like:
package main
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestServer(t *testing.T) {
t.Run("slowHandler should return 503 if request timeouts", func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/slow", nil)
slowHandler(w, r) // Using the handler directly
expected := http.StatusServiceUnavailable
got := w.Result().StatusCode
if got != expected {
t.Errorf("expected request to cause %v, got %v", expected, got)
}
})
}
In the first test we would get:
Working on it...
Operation timed out.
PASS
ok test 5.451s
And on the second:
Working on it...
--- FAIL: TestServer (0.00s)
--- FAIL: TestServer/slowHandler_should_return_503_if_request_timeouts (10.01s)
main_test.go:39: expected request to cause 503, got 200
FAIL
exit status 1
FAIL test 10.230s
So we have the first test that also tests the implementation of the server returning a correct output, and the second one that tests purely the handler failing, as it does not know about the timeout. Also, it would produce the same outcome using a totally different request like httptest.NewRequest(http.MethodDelete, "/", nil)
.
Should handler tests know about the server? Are there any cons if they are tested using the custom server?