Golang中如何使用httptest.NewRequest包含带值的路径?

Golang中如何使用httptest.NewRequest包含带值的路径? 我正在尝试按照《重温HTTP处理器 | main | 通过测试学习Go》中描述的方法测试HTTP处理器。

我有以下代码(为简洁起见已精简):

email := "myemail@test.me"

urlPath := fmt.Sprintf("http://test.com/%s", email)

request := httptest.NewRequest(http.MethodPost, urlPath, nil)
response := httptest.NewRecorder()

handleEmailCheck(response, request)

路由类似于:

router.HandleFunc("POST /users/{email}", handleEmailCheck)

我相信路由/路由器没有被使用,因为我只是在单独测试处理器。我尝试过:

urlPath := fmt.Sprintf("http://test.com/users/%s", email)

urlPath := fmt.Sprintf("/%s", email)

但它总是返回400,因为以下代码失败(返回“”):

email := r.PathValue("email")

如果我运行应用程序并用Postman测试,它工作正常。httptest 是否需要任何特殊要求或设置才能传递路径值?


更多关于Golang中如何使用httptest.NewRequest包含带值的路径?的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

非常感谢你们两位。我已经尝试了两种方法,并且都按预期工作了。不过,我打算采用 @GonzaSaya 的方法,因为在我看来它更简单、更清晰。

更多关于Golang中如何使用httptest.NewRequest包含带值的路径?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


不过,我选择采用 @GonzaSaya 的方法,因为在我看来它更简单、更清晰。

这取决于你要测试什么。启用代码覆盖率并测试两种解决方案,你就会明白我的意思。话虽如此,两种方法都很好,我很高兴你找到了解决方案!

尝试:

r, err := http.NewRequest(method, path, body)
r.SetPathValue("key", "value")

示例位于:

图片

GitHub - saya-ML/golang-test-params

通过创建GitHub账户来为saya-ML/golang-test-params的开发做出贡献。

在测试HTTP处理器时,httptest.NewRequest创建的请求不会自动解析路径参数。你需要手动设置路径值,或者使用路由器来解析。以下是两种解决方案:

方案1:手动设置路径值(推荐用于单元测试)

在测试中直接设置PathValue

email := "myemail@test.me"

request := httptest.NewRequest(http.MethodPost, "/users/"+email, nil)
// 手动设置路径参数
request.SetPathValue("email", email)

response := httptest.NewRecorder()
handleEmailCheck(response, request)

方案2:使用路由器进行测试(集成测试)

如果你需要测试完整的路由解析,应该使用路由器:

email := "myemail@test.me"

router := http.NewServeMux()
router.HandleFunc("POST /users/{email}", handleEmailCheck)

request := httptest.NewRequest(http.MethodPost, "/users/"+email, nil)
response := httptest.NewRecorder()

router.ServeHTTP(response, request)

完整示例代码

func TestHandleEmailCheck(t *testing.T) {
    email := "myemail@test.me"
    
    // 方案1:直接设置路径值
    t.Run("unit test with manual path value", func(t *testing.T) {
        request := httptest.NewRequest(http.MethodPost, "/users/"+email, nil)
        request.SetPathValue("email", email)
        
        response := httptest.NewRecorder()
        handleEmailCheck(response, request)
        
        if response.Code != http.StatusOK {
            t.Errorf("expected status OK, got %v", response.Code)
        }
    })
    
    // 方案2:使用路由器
    t.Run("integration test with router", func(t *testing.T) {
        router := http.NewServeMux()
        router.HandleFunc("POST /users/{email}", handleEmailCheck)
        
        request := httptest.NewRequest(http.MethodPost, "/users/"+email, nil)
        response := httptest.NewRecorder()
        
        router.ServeHTTP(response, request)
        
        if response.Code != http.StatusOK {
            t.Errorf("expected status OK, got %v", response.Code)
        }
    })
}

处理器示例

func handleEmailCheck(w http.ResponseWriter, r *http.Request) {
    email := r.PathValue("email")
    if email == "" {
        http.Error(w, "email parameter required", http.StatusBadRequest)
        return
    }
    
    // 处理逻辑
    fmt.Fprintf(w, "Processing email: %s", email)
}

使用SetPathValue方法是Go 1.22+中引入的,专门用于在测试中模拟路径参数。对于单元测试处理器逻辑,这是最直接的方法。

我认为路由/路由器没有被使用,因为我只是在单独测试处理器。

确实如此。在你的测试中,你需要做一些事情来设置你的多路复用器/路由。查看 http/httptest 中的示例。这篇帖子展示了如何编写一些测试:

CloudBees CloudBees

在 Go 中测试你的(HTTP)处理器

在 Go 中构建 Web(HTTP)服务?让我们谈谈如何对你的处理器函数进行单元测试。

另一种方法是将你的多路复用器逻辑拆分出来,然后在测试中直接复用。类似这样:

DEV Community DEV Community

在 Go 中测试 HTTP 处理器

简介 在这个例子中,我将使用 httptest 库创建单元测试,我们…

采用第二种方案,让我们根据你想要测试的内容创建一个示例:

func main() {
	mux := newAppMux()
	http.ListenAndServe(":9090", mux)
}

func newAppMux() *http.ServeMux {
	router := http.NewServeMux()
	router.HandleFunc("POST /users/{email}", handleEmailCheck)
	return router
}

func handleEmailCheck(w http.ResponseWriter, r *http.Request) {
	email := r.PathValue("email")
	if len(email) > 0 {
		fmt.Fprintf(w, `{ "email" : "%v" }`, email)
	} else {
		w.WriteHeader(http.StatusNotFound)
		fmt.Fprint(w, http.StatusText(http.StatusNotFound))
	}
}

测试方法如下:

func TestHandleEmailCheck(t *testing.T) {
	ts := httptest.NewServer(newAppMux())
	defer ts.Close()

	client := ts.Client()
	res, err := client.Post(fmt.Sprintf("%v/users/test@test.com", ts.URL), "application/json", strings.NewReader(`{ "request": "body" }`))
	if err != nil {
		t.Errorf("Wasn't expecting error. Got: %v", err)
	}

	resBody, err := io.ReadAll(res.Body)
	res.Body.Close()
	if err != nil {
		t.Errorf("Wasn't expecting error. Got: %v", err)
	}
	expecting := `{ "email" : "test@test.com" }`
	if string(resBody) != expecting {
		t.Errorf("Unexpected response.\n\nExpected: %v\nGot: %s", expecting, resBody)
	}
}

希望这些足以让你开始!

回到顶部