0

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

1 Answer 1

2

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() ... } 
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you, I did not find much information on testing my controller, so this is really helpful

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.