I'm writting a simple rest boiler plate for future projects, I'm currently working on some tests for my controller, I'm trying to retrieve a todo by it's Id at /todo/{id}
, here's the handler.
func (t TodoController) GetById(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
id, err := strconv.Atoi(params["id"])
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
todo, err := t.todoService.GetById(id)
if err != nil {
w.WriteHeader(http.StatusNotFound)
return
}
helpers.SendResponse(http.StatusOK, todo, w)
}
And here's the test for this controller.
var (
todoController TodoController
recorder *httptest.ResponseRecorder
todos []models.Todo
)
func setup() {
todoController = *NewTodoController(services.NewTodoService())
recorder = httptest.NewRecorder()
todos = []models.Todo{
{
ID: 0,
Todo: "Buy milk",
Completed: false,
},
{
ID: 1,
Todo: "Buy cheese",
Completed: false,
},
{
ID: 2,
Todo: "Buy eggs",
Completed: false,
},
}
}
func TestGetById(t *testing.T) {
// Arrange
setup()
request := httptest.NewRequest(http.MethodGet, "/todo/1", nil)
var response models.Todo
// Act
todoController.GetById(recorder, request)
result := recorder.Result()
defer result.Body.Close()
data, err := ioutil.ReadAll(result.Body)
err = json.Unmarshal(data, &response)
// Assert
if err != nil {
t.Errorf("Expected error to be nil but got %v", err)
}
assert.Equal(t, result.StatusCode, http.StatusOK, "Response should have been 200 Ok")
assert.Equal(t, response, todos[1], "Response did not match the expected result")
}
It looks like when sending a request to /todo/1
mux is not able to retrieve the Id, so it end up returning a BadRequest error.
Here's a link to this repo: https://github.com/Je12emy/rest_boiler
CodePudding user response:
Mux provides two ways to inject request params - SetURLVars
helper and using mux.Router
wrapper instead of direct handler call.
SetURLVars does exactly what you want - injects otherwise not accessible parameters into HTTP request.
This method is simple, but has one issue though. Used URL and injected parameters are out of sync.
None forbids developers to write this code:
// we use 2 in request path
request := httptest.NewRequest(http.MethodGet, "/todo/2", nil)
// and we use 1 in request param
request = SetURLVars(request, map[string]string{"id":"1"})
This is not very clean testing practice.
We do not test if our var names in routing is correct. We can use item_id
in router instead of id
and test does not catch that.
Not only that, but we can use wrong path in router definition and map different handler. Client can delete order
instead of todo
item if we make that mistake.
That could be solved if we use our production Router in test.
Assume it is our production code:
func InitRouter(t TodoController) http.handler
r := mux.NewRouter()
r.HandleFunc("/todo/{id}", t.GetById).Methods(http.MethodGet)
return r
In test, we can test GetById
through router we created in InitRouter
function:
func TestGetById(t *testing.T) {
// Arrange
setup()
request := httptest.NewRequest(http.MethodGet, "/todo/1", nil)
var response models.Todo
// added setup
sut := InitRouter(todoController)
// Act
// changed act - calling GetById through production router
sut.ServeHTTP(recorder, request)
// no chnages after that
result := recorder.Result()
defer result.Body.Close()
...
}