danielgtaylor / danielgtaylor/huma
Best practice for middleware testing
- Dominant language
- Go
- Stars
- 4.4k
- Forks
- 285
- Avg merge
- 40m
- Merged PRs (30d)
- 1
Description
# Description
When recently writing tests for a huma router-agnostic middleware I didn't feel that there was an obvious pattern. I wasn't sure if this was an issue on my end, so I'd like explore the best way to test huma router-agnostic middleware and either contribute a documentation change or an update to the humatest package to make it easier for people to develop Huma router-agnostic middleware.
# Example
Lets say I have some authentication middleware that does the following:
* introspects headers and route parameters to authenticate the incoming request (returns a 401 if invalid)
* Sets a `claims` value on the context to be used by other middleware or request handlers.
File under test...
```go
package server
import (
"net/http"
"github.com/danielgtaylor/huma/v2"
)
var (
claimsContextKey string = "claims"
)
type Claims struct {
Claims []string
}
func authMiddleware(api huma.API) func(huma.Context, func(huma.Context)) {
return func(ctx huma.Context, next func(huma.Context)) {
authHeader := ctx.Header("Authorization")
if authHeader == "" {
huma.WriteErr(api, ctx, http.StatusUnauthorized, "no authorization header present")
return
}
c := Claims{
Claims: []string{"foo", "bar"},
}
authorizedCtx := huma.WithValue(ctx, claimsContextKey, c)
next(authorizedCtx)
}
}
```
Test file...
```go
package server
import (
"context"
"net/http"
"testing"
"github.com/danielgtaylor/huma/v2"
"github.com/danielgtaylor/huma/v2/humatest"
"github.com/stretchr/testify/assert"
)
func TestAuthMiddleware(t *testing.T) {
tests := []struct {
desc string
header string
expCode int
expClaims Claims
}{
{
desc: "no auth header",
header: "",
expCode: 401,
expClaims: Claims{},
},
{
desc: "correct header",
header: "Authorization: bearer foo",
expCode: 204,
expClaims: Claims{Claims: []string{"foo", "bar"}},
},
}
for _, tt := range tests {
t.Run(tt.desc, func(*testing.T) {
_, api := humatest.New(t)
api.UseMiddleware(authMiddleware(api))
huma.Register(api, huma.Operation{
Method: http.MethodGet,
Path: "/test",
Summary: "test",
Description: "test route",
}, func(ctx context.Context, _ *struct{}) (*struct{}, error) {
if tt.expCode == 204 {
claims, ok := ctx.Value(claimsContextKey).(Claims)
assert.True(t, ok)
assert.Equal(t, tt.expClaims, claims)
}
return nil, nil
})
resp := api.Get("/test", tt.header)
if tt.expCode != 204 {
assert.Equal(t, tt.expCode, resp.Code)
}
})
}
}
```
# Comments / Concerns
I found what felt off during testing was that I didn't have a way to work directly with the context but rather had to use the api.get pattern to invoke the middleware itself. In order to make any assertions about how that middleware changed the context I had to create a custom operation which itself contained the asserts. The other thing that felt off here was that depending on what happened, I needed to make my assertions in different places, in the case of a 4xx the next handler would never be called and instead I would have to check on the API itself.
# Questions
1. Is this the best approach to test middleware?
2. If it is, is there some utility we can provide in the `humatest` package to streamline this process?
Contributor guide
No contributing guide indexed for this repository
Research direction
Start by reviewing the humatest.New setup, api.UseMiddleware, and api.Get pattern shown in the example, along with the middleware and test files from the issue. Determine whether the request should become documentation or a humatest utility, and define done as an agreed testing approach or a scoped utility change with coverage for both rejected requests and context values.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- api, testing
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 30/100