Hi, I’m fairly new to Go and looking for some guidance testing a HTTP server implementation.
The behaviors I’m trying to test
- Request received on /mypath
- Method must be a POST; all other methods return 405 error
- Request must have a valid JSON body
- missing body send 422
- missing required fields send 422
- Request must have a valid JSON body
- Method must be a POST; all other methods return 405 error
These behaviors are written by me. Maybe I’m misguided by my understanding of RESTful best practice. I’m trying to avoid the frustrations I’ve experienced implementing a API client due to inaccurate status codes. I want users of my API to be able to understand what is wrong with their client quickly and I believe accurate status codes will assist with this.
My tests looks like:
func TestServer(t *testing.T) {
server := NewServer()
t.Run("Send GET, expect 405", func(t *testing.T) {
request, _ := http.NewRequest(http.MethodGet, "/myPath", nil)
response := httptest.NewRecorder()
server.ServeHTTP(response, request)
assertResponseStatus(t, got, http.StatusMethodNotAllowed)
})
t.Run("Send POST without body, expect 422", func(t *testing.T) {
var body = []byte(``)
request, _ := http.NewRequest(http.MethodPost, "/myPath", nil)
response := httptest.NewRecorder()
request.Header.Set("Content-Type", JSONContentType)
server.ServeHTTP(response, request)
assertResponseStatus(t, got, http.StatusUnprocessableEntity)
})
}
I realize there is some duplication here, I haven’t refactored yet. I also haven’t written tests for “all other methods return 405”, just GET so far. And I won’t touch on “missing required fields send 422” yet.
I’m using gorilla/mux.
“Method must be a POST all other methods return 405” is satisfied by
router.Handle("/mypath", http.HandlerFunc(myPathHandler)).Methods("POST")
“Send POST without body, expect 422” is where I’m having issues.
How do I build a request with an empty body? I’ve tried
request, _ := http.NewRequest(http.MethodPost, "/mypath", nil)
but this results in a Null Pointer when trying
func myPathHandler(w http.ResponseWriter, r *http.Request) {
myPathOpts := &myPathRequestBody{}
err := json.NewDecoder(r.Body).Decode(myPathOpts)
...
}
So I try
func myPathHandler(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
switch {
case err == io.EOF:
http.Error(w, err.Error(), http.StatusUnprocessableEntity)
case err != nil:
http.Error(w, err.Error(), http.StatusInternalServerError)
}
...
}
but this is also a Null Pointer
I build the request differently
request, _ := http.NewRequest(http.MethodPost, "/myPath", bytes.NewBuffer(nil))
or
var body = []byte(``)
request, _ := http.NewRequest(http.MethodPost, "/myPath", bytes.NewBuffer(body))
but I don’t get an io.EOF or an err != nil
Add a switch case to test body == nil
func myPathHandler(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
switch {
case body == nil:
http.Error(w, err.Error(), http.StatusUnprocessableEntity)
case err == io.EOF:
http.Error(w, err.Error(), http.StatusUnprocessableEntity)
case err != nil:
http.Error(w, err.Error(), http.StatusInternalServerError)
}
...
}
no luck there either.
Goodness, my ? seems a bit verbose. Thank you for your time.
Guidance and criticism greatly appreciated.
Kindly, Matt