unit-testinggohttprouter

(why) Is it necessary to path parameters via request context?


I'm working on a simple toy API in Golang using httprouter and trying to write tests for my endpoints. One of my endpoints is a "show movie" endpoint with a path like /v1/movies/:id . For this endpoint, I initially wrote the test creating the request like `

req := httptest.NewRequest(http.MethodGet, "/v1/movies/"+tc.id, 

but for some reason this wasn't enough. After a lot of googling, it turns out I have to do something like the below, between the //**** comments, where the params are manually added to the request context. I'm confused why this is necessary, since shouldn't the handler receive the ID in the path the way I wrote it above? This isn't necessary when writing regular client code (I think). Looking for an explanation on why this is necessary, if it's only for testing purposes, or what. TIA

func TestShowMovieHandler(t *testing.T) {
    app := &application{}

    testCases := []struct {
        name           string
        id             string
        expectedStatus int
        expectedBody   string
    }{
        {
            name:           "Valid ID",
            id:             "1",
            expectedStatus: http.StatusOK,
            expectedBody:   "getting movie 1\n",
        }
    for _, tc := range testCases {
        t.Run(tc.name, func(t *testing.T) {
            // Create a request with the specified ID
            req := httptest.NewRequest(http.MethodGet, "/v1/movies/"+tc.id, nil)
            rr := httptest.NewRecorder()

                        //**** why is this necessary
            params := httprouter.Params{httprouter.Param{Key: "id", Value: tc.id}}
            t.Logf("%#v\n", httprouter.ParamsKey)
            t.Logf("%#v\n", params)
            ctx := context.WithValue(req.Context(), httprouter.ParamsKey, params)
            req = req.WithContext(ctx)
                        //****

            app.showMovieHandler(rr, req)

            if rr.Code != tc.expectedStatus {
                t.Errorf("expected status %v; got %v", tc.expectedStatus, rr.Code)
            }

            if rr.Body.String() != tc.expectedBody {
                t.Errorf("expected body %q; got %q", tc.expectedBody, rr.Body.String())
            }
        })
    }
}

I was able to find a solution but seeking explanation why it works


Solution

  • The router processes the path parameters. The test does not use the router. Fix by using a router.

    t.Run(tc.name, func(t *testing.T) {
        // Create a request with the specified ID
        req := httptest.NewRequest(http.MethodGet, "/v1/movies/"+tc.id, nil)
        rr := httptest.NewRecorder()
        router := httprouter.New()
        router.HandlerFunc("GET", "/v1/movies/:id", app.showMovieHandler)
        router.ServeHTTP(rr, req)
    
        if rr.Code != tc.expectedStatus {
            t.Errorf("expected status %v; got %v", tc.expectedStatus, rr.Code)
        }
    
        if rr.Body.String() != tc.expectedBody {
            t.Errorf("expected body %q; got %q", tc.expectedBody, rr.Body.String())
        }
    })