Golang中测试中间件的实现方法与最佳实践

Golang中测试中间件的实现方法与最佳实践 你好,

我正在尝试测试下面的中间件,虽然测试通过了,但代码覆盖率始终为0。我遗漏了什么吗?

谢谢

func Timing(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		timer := time.Now()
		next.ServeHTTP(w, r)

		if ID, ok := r.Context().Value("something"); ok {
			log.Info(timer.String())
		}
	}
}
func TestTiming(t *testing.T) {
	timingHandler := func(w http.ResponseWriter, r *http.Request) {
		r.WithContext(context.WithValue(r.Context(), "something", "hello"))
	}

	req := httptest.NewRequest(http.MethodGet, "http://www.example.com", nil)
	res := httptest.NewRecorder()
	timingHandler(res, req)

	tim := Timing(timingHandler)
	tim.ServeHTTP(res, req)

	assert.Equal(t, http.StatusOK, res.Result().StatusCode)
}

更多关于Golang中测试中间件的实现方法与最佳实践的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

抱歉,是虚惊一场!测试实际上运行正常。留在这里以防有人需要这样的东西。

更多关于Golang中测试中间件的实现方法与最佳实践的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你的测试代码覆盖率之所以为0,是因为中间件中的日志记录逻辑实际上没有被执行。问题在于timingHandler函数中创建的上下文没有被正确传递。r.WithContext()返回的是一个新的请求副本,但你没有使用它,所以原始请求的上下文并没有被修改。

以下是修复后的测试代码:

func TestTiming(t *testing.T) {
    // 创建一个模拟的处理器,它会设置上下文值
    nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 创建带值的上下文并应用到请求
        ctx := context.WithValue(r.Context(), "something", "hello")
        r = r.WithContext(ctx)
        w.WriteHeader(http.StatusOK)
    })

    // 创建测试请求和响应记录器
    req := httptest.NewRequest(http.MethodGet, "http://example.com", nil)
    res := httptest.NewRecorder()

    // 应用中间件
    handler := Timing(nextHandler)
    
    // 执行处理
    handler.ServeHTTP(res, req)

    // 验证状态码
    if res.Code != http.StatusOK {
        t.Errorf("expected status OK, got %v", res.Code)
    }
}

如果你想要更完整的测试,包括验证日志是否被记录,可以使用httptestResponseRecorder并模拟日志记录器:

func TestTiming_WithLogging(t *testing.T) {
    // 捕获日志输出
    var logOutput bytes.Buffer
    log.SetOutput(&logOutput)
    defer log.SetOutput(os.Stderr)

    nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        ctx := context.WithValue(r.Context(), "something", "hello")
        r = r.WithContext(ctx)
        w.WriteHeader(http.StatusOK)
    })

    req := httptest.NewRequest(http.MethodGet, "http://example.com", nil)
    res := httptest.NewRecorder()

    handler := Timing(nextHandler)
    handler.ServeHTTP(res, req)

    // 验证日志是否包含时间信息
    logStr := logOutput.String()
    if !strings.Contains(logStr, "time=") {
        t.Error("expected timing log output")
    }
}

对于中间件测试的最佳实践,我建议:

  1. 测试中间件的核心逻辑:确保上下文值存在时日志被记录
  2. 测试中间件的传递性:确保下一个处理器被正确调用
  3. 使用表格驱动测试:测试不同的场景
func TestTiming_TableDriven(t *testing.T) {
    tests := []struct {
        name           string
        contextValue   interface{}
        expectLog      bool
        expectedStatus int
    }{
        {
            name:           "with context value",
            contextValue:   "test-id",
            expectLog:      true,
            expectedStatus: http.StatusOK,
        },
        {
            name:           "without context value",
            contextValue:   nil,
            expectLog:      false,
            expectedStatus: http.StatusOK,
        },
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            var logOutput bytes.Buffer
            log.SetOutput(&logOutput)
            defer log.SetOutput(os.Stderr)

            nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                if tt.contextValue != nil {
                    ctx := context.WithValue(r.Context(), "something", tt.contextValue)
                    r = r.WithContext(ctx)
                }
                w.WriteHeader(tt.expectedStatus)
            })

            req := httptest.NewRequest(http.MethodGet, "http://example.com", nil)
            res := httptest.NewRecorder()

            handler := Timing(nextHandler)
            handler.ServeHTTP(res, req)

            // 验证状态码
            if res.Code != tt.expectedStatus {
                t.Errorf("expected status %v, got %v", tt.expectedStatus, res.Code)
            }

            // 验证日志
            hasLog := strings.Contains(logOutput.String(), "time=")
            if tt.expectLog && !hasLog {
                t.Error("expected log output but got none")
            }
            if !tt.expectLog && hasLog {
                t.Error("expected no log output but got some")
            }
        })
    }
}

这些修改应该能解决你的代码覆盖率问题,并提供更全面的中间件测试。

回到顶部