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 :)
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 =)
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With