I am trying to build and test a very basic API in Go to learn more about the language after following their tutorial. The API and the four routes defined work in Postman and the browser, but when trying to write the test for any of the routes, the ResponseRecorder doesn't have a body, so I cannot verify it is correct.
I followed the example here and it works, but when I change it for my route, there is no response.
Here is my main.go
file.
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"github.com/gorilla/mux"
)
// A Person represents a user.
type Person struct {
ID string `json:"id,omitempty"`
Firstname string `json:"firstname,omitempty"`
Lastname string `json:"lastname,omitempty"`
Location *Location `json:"location,omitempty"`
}
// A Location represents a Person's location.
type Location struct {
City string `json:"city,omitempty"`
Country string `json:"country,omitempty"`
}
var people []Person
// GetPersonEndpoint returns an individual from the database.
func GetPersonEndpoint(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "application/json")
params := mux.Vars(req)
for _, item := range people {
if item.ID == params["id"] {
json.NewEncoder(w).Encode(item)
return
}
}
json.NewEncoder(w).Encode(&Person{})
}
// GetPeopleEndpoint returns all people from the database.
func GetPeopleEndpoint(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(people)
}
// CreatePersonEndpoint creates a new person in the database.
func CreatePersonEndpoint(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "application/json")
params := mux.Vars(req)
var person Person
_ = json.NewDecoder(req.Body).Decode(&person)
person.ID = params["id"]
people = append(people, person)
json.NewEncoder(w).Encode(people)
}
// DeletePersonEndpoint deletes a person from the database.
func DeletePersonEndpoint(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "application/json")
params := mux.Vars(req)
for index, item := range people {
if item.ID == params["id"] {
people = append(people[:index], people[index+1:]...)
break
}
}
json.NewEncoder(w).Encode(people)
}
// SeedData is just for this example and mimics a database in the 'people' variable.
func SeedData() {
people = append(people, Person{ID: "1", Firstname: "John", Lastname: "Smith", Location: &Location{City: "London", Country: "United Kingdom"}})
people = append(people, Person{ID: "2", Firstname: "John", Lastname: "Doe", Location: &Location{City: "New York", Country: "United States Of America"}})
}
func main() {
router := mux.NewRouter()
SeedData()
router.HandleFunc("/people", GetPeopleEndpoint).Methods("GET")
router.HandleFunc("/people/{id}", GetPersonEndpoint).Methods("GET")
router.HandleFunc("/people/{id}", CreatePersonEndpoint).Methods("POST")
router.HandleFunc("/people/{id}", DeletePersonEndpoint).Methods("DELETE")
fmt.Println("Listening on http://localhost:12345")
fmt.Println("Press 'CTRL + C' to stop server.")
log.Fatal(http.ListenAndServe(":12345", router))
}
Here is my main_test.go
file.
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
)
func TestGetPeopleEndpoint(t *testing.T) {
req, err := http.NewRequest("GET", "/people", nil)
if err != nil {
t.Fatal(err)
}
// We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
rr := httptest.NewRecorder()
handler := http.HandlerFunc(GetPeopleEndpoint)
// Our handlers satisfy http.Handler, so we can call their ServeHTTP method
// directly and pass in our Request and ResponseRecorder.
handler.ServeHTTP(rr, req)
// Trying to see here what is in the response.
fmt.Println(rr)
fmt.Println(rr.Body.String())
// Check the status code is what we expect.
if status := rr.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v",
status, http.StatusOK)
}
// Check the response body is what we expect - Commented out because it will fail because there is no body at the moment.
// expected := `[{"id":"1","firstname":"John","lastname":"Smith","location":{"city":"London","country":"United Kingdom"}},{"id":"2","firstname":"John","lastname":"Doe","location":{"city":"New York","country":"United States Of America"}}]`
// if rr.Body.String() != expected {
// t.Errorf("handler returned unexpected body: got %v want %v",
// rr.Body.String(), expected)
// }
}
I appreciate that I am probably making a beginner mistake, so please take mercy on me. I have read a number of blogs testing mux, but can't see what I have done wrong.
Thanks in advance for your guidance.
UPDATE
Moving my SeeData call to init()
resolved the body being empty for the people call.
func init() {
SeedData()
}
However, I now have no body returned when testing a specific id.
func TestGetPersonEndpoint(t *testing.T) {
id := 1
path := fmt.Sprintf("/people/%v", id)
req, err := http.NewRequest("GET", path, nil)
if err != nil {
t.Fatal(err)
}
// We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
rr := httptest.NewRecorder()
handler := http.HandlerFunc(GetPersonEndpoint)
// Our handlers satisfy http.Handler, so we can call their ServeHTTP method
// directly and pass in our Request and ResponseRecorder.
handler.ServeHTTP(rr, req)
// Check request is made correctly and responses.
fmt.Println(path)
fmt.Println(rr)
fmt.Println(req)
fmt.Println(handler)
// expected response for id 1.
expected := `{"id":"1","firstname":"John","lastname":"Smith","location":{"city":"London","country":"United Kingdom"}}` + "\n"
if status := rr.Code; status != http.StatusOK {
message := fmt.Sprintf("The test returned the wrong status code: got %v, but expected %v", status, http.StatusOK)
t.Fatal(message)
}
if rr.Body.String() != expected {
message := fmt.Sprintf("The test returned the wrong data:\nFound: %v\nExpected: %v", rr.Body.String(), expected)
t.Fatal(message)
}
}
Moving my SeedData call to init()
resolved the body being empty for the people call.
func init() {
SeedData()
}
Creating a new router instance resolved the issue with accessing a variable on a route.
rr := httptest.NewRecorder()
router := mux.NewRouter()
router.HandleFunc("/people/{id}", GetPersonEndpoint)
router.ServeHTTP(rr, req)
I think its because your test isn't including the router hence the path variables aren't being detected. Here, try this
and in your testcase, initiatize from the router method like below
And now if you try accessing the path variable id, it should be present in the map retured by mux since mux registered it when you initiatlized the Handler from mux Router instance returned from router()
Also like you mentioned, use the init function for one time setups.