使用 httptest.NewRecorder 可捕获中间件输出,无需启动真实服务器;它将响应头、状态码、响应体缓存在内存中,调用 ServeHTTP 后方可读取 rec.Code 等字段。

用 httptest.NewRecorder 捕获中间件输出
中间件本质是包装 http.Handler 的函数,测试它不需要启动真实服务器。关键在于构造一个能接收响应的 http.ResponseWriter 实例——httptest.NewRecorder() 正是为此设计。
- 它不向网络写入,所有响应头、状态码、响应体都缓存在内存中,方便断言
- 直接传给中间件包装后的 handler,再调用
ServeHTTP即可触发完整链路 - 注意:必须在调用
ServeHTTP后读取recorder.Code、recorder.Body.String()等字段,否则为空
func TestAuthMiddleware(t *testing.T) {
// 原始 handler(被中间件包装的目标)
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
})
// 应用中间件
handler := AuthMiddleware(next)
// 构造请求和 recorder
req := httptest.NewRequest("GET", "/api/data", nil)
rec := httptest.NewRecorder()
// 触发中间件 + handler 执行
handler.ServeHTTP(rec, req)
// 断言中间件行为(例如未授权时返回 401)
if rec.Code != http.StatusUnauthorized {
t.Errorf("expected status %d, got %d", http.StatusUnauthorized, rec.Code)
}
}
模拟请求上下文与依赖(如 JWT 解析)
真实中间件常依赖外部服务(如 Redis 校验 token、数据库查用户),测试时应剥离这些依赖,否则变成集成测试且不可靠。
- 把依赖抽象为接口(如
TokenValidator),测试时注入 mock 实现 - 避免在测试中调用
os.Setenv或修改全局变量;改用构造函数参数或选项模式传入配置 - 对时间敏感逻辑(如 token 过期检查),使用可注入的
time.Now替代(如func() time.Time类型字段)
type MockValidator struct{}
func (m MockValidator) Validate(token string) (*User, error) {
if token == "valid-token" {
return &User{ID: 123}, nil
}
return nil, errors.New("invalid token")
}
func TestAuthMiddleware_WithMockValidator(t *testing.T) {
validator := MockValidator{}
handler := AuthMiddlewareWithValidator(http.HandlerFunc(okHandler), validator)
req := httptest.NewRequest("GET", "/api/data", nil)
req.Header.Set("Authorization", "Bearer valid-token")
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatal("expected 200 for valid token")
}
}
测试中间件链顺序与组合行为
多个中间件嵌套时(如 Logging → Auth → RateLimit → Handler),错误顺序会导致上下文丢失或 panic。测试重点不是单个中间件,而是它们协作时的状态流转。
- 用
req.Context()检查中间件是否正确设置值(如ctx.Value("user_id")) - 在目标 handler 中主动读取 context 并写入响应,验证前序中间件是否完成赋值
- 手动构造中间件链,避免依赖框架注册顺序(如 Gin 的
Use()或 Chi 的Use())
func TestMiddlewareChain_ContextPropagation(t *testing.T) {
final := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
userID := r.Context().Value("user_id")
if userID == nil {
http.Error(w, "missing user_id in context", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "user: %v", userID)
})
chain := LoggingMiddleware(AuthMiddleware(final))
req := httptest.NewRequest("GET", "/api/data", nil)
req.Header.Set("Authorization", "Bearer valid-token")
rec := httptest.NewRecorder()
chain.ServeHTTP(rec, req)
if rec.Code != http.StatusOK || !strings.Contains(rec.Body.String(), "user: 123") {
t.Error("context not propagated correctly")
}
}
避免常见陷阱:panic、空指针、未处理错误
中间件里常见错误是忽略 error 返回或对 nil 请求/响应操作,测试时要主动触发这些边界情况。
立即学习“go语言免费学习笔记(深入)”;
- 传入
nil请求对象,确认中间件不 panic(应有防御性检查) - 用空或非法 header(如
"Bearer ")测试 token 解析逻辑 - 若中间件内部调用
next.ServeHTTP前 panic,下游 handler 不会执行——需在 recorder 中看到预期错误响应,而非无响应 - 不要只测“成功路径”,至少覆盖 1 个失败分支(如 token 缺失、签名无效、过期)
最易被忽略的是中间件对 http.Hijacker、http.Pusher 等接口的透传问题——如果中间件返回自定义 ResponseWriter 但没实现这些接口,下游 handler 调用 Push() 会 panic。测试时可用类型断言验证:_, ok := rec.(http.Pusher)。










