Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing Chi Routes w/Path Variables

Tags:

rest

go

go-chi

I'm having trouble testing my go-chi routes, specifically the route with path variables. Running the server with go run main.go works fine and requests to the route with the path variable behaves as expected.

When I run my tests for the routes, I always get the HTTP error: Unprocessable Entity. After logging out what's happening with articleID, it seems like the articleCtx isn't getting access to the path variable. Not sure if this means I need to use articleCtx in the tests, but I've tried ArticleCtx(http.HandlerFunc(GetArticleID)) and get the error:

panic: interface conversion: interface {} is nil, not *chi.Context [recovered] panic: interface conversion: interface {} is nil, not *chi.Context

Running the server: go run main.go

Testing the server: go test .

My source:

// main.go

package main

import (
    "context"
    "fmt"
    "net/http"
    "strconv"

    "github.com/go-chi/chi"
)

type ctxKey struct {
    name string
}

func main() {
    r := chi.NewRouter()

    r.Route("/articles", func(r chi.Router) {
        r.Route("/{articleID}", func(r chi.Router) {
            r.Use(ArticleCtx)
            r.Get("/", GetArticleID) // GET /articles/123
        })
    })

    http.ListenAndServe(":3333", r)
}

// ArticleCtx gives the routes using it access to the requested article ID in the path
func ArticleCtx(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        articleParam := chi.URLParam(r, "articleID")
        articleID, err := strconv.Atoi(articleParam)
        if err != nil {
            http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
            return
        }

        ctx := context.WithValue(r.Context(), ctxKey{"articleID"}, articleID)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

// GetArticleID returns the article ID that the client requested
func GetArticleID(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    articleID, ok := ctx.Value(ctxKey{"articleID"}).(int)
    if !ok {
        http.Error(w, http.StatusText(http.StatusUnprocessableEntity), http.StatusUnprocessableEntity)
        return
    }

    w.Write([]byte(fmt.Sprintf("article ID:%d", articleID)))
}
// main_test.go

package main

import (
    "fmt"
    "net/http"
    "net/http/httptest"
    "testing"
)

func TestGetArticleID(t *testing.T) {
    tests := []struct {
        name           string
        rec            *httptest.ResponseRecorder
        req            *http.Request
        expectedBody   string
        expectedHeader string
    }{
        {
            name:         "OK_1",
            rec:          httptest.NewRecorder(),
            req:          httptest.NewRequest("GET", "/articles/1", nil),
            expectedBody: `article ID:1`,
        },
        {
            name:         "OK_100",
            rec:          httptest.NewRecorder(),
            req:          httptest.NewRequest("GET", "/articles/100", nil),
            expectedBody: `article ID:100`,
        },
        {
            name:         "BAD_REQUEST",
            rec:          httptest.NewRecorder(),
            req:          httptest.NewRequest("PUT", "/articles/bad", nil),
            expectedBody: fmt.Sprintf("%s\n", http.StatusText(http.StatusBadRequest)),
        },
    }

    for _, test := range tests {
        t.Run(test.name, func(t *testing.T) {
            ArticleCtx(http.HandlerFunc(GetArticleID)).ServeHTTP(test.rec, test.req)

            if test.expectedBody != test.rec.Body.String() {
                t.Errorf("Got: \t\t%s\n\tExpected: \t%s\n", test.rec.Body.String(), test.expectedBody)
            }
        })
    }
}

Not sure how to continue with this. Any ideas? I was wondering if there was an answer in net/http/httptest about using context with tests but didn't see anything.

Also pretty new go Go (and the context package), so any code review / best practice comments are greatly appreciated :)

like image 786
John Avatar asked Oct 16 '22 07:10

John


2 Answers

Had a similar issue, although I was unit testing a handler directly. Basically, it seems like the url parameters are not auto added to the request context when using httptest.NewRequest forcing you to manually add them.

Something like the following worked for me.

w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/{key}", nil)

rctx := chi.NewRouteContext()
rctx.URLParams.Add("key", "value")

r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))

handler := func(w http.ResponseWriter, r *http.Request) {
    value := chi.URLParam(r, "key")
}
handler(w, r)

All credit to soedar here =)

like image 131
Ullauri Avatar answered Oct 20 '22 15:10

Ullauri


I had the same problem with named path variables. I was able to resolve it setting up router for my tests. A good sample is shown in go-chi tests.

Go Chi Sample test with URL params

like image 44
Gaurav Verma Avatar answered Oct 20 '22 16:10

Gaurav Verma