Go语言的标准库提供了完善的HTTP相关工具,其中net/http/httptest包专门用于HTTP接口的测试场景,能够帮助开发者在不启动真实网络服务的情况下,模拟HTTP请求并验证接口的返回结果,是Go测试HTTP接口的核心工具。

基础测试方案:使用httptest模拟请求
最常见的HTTP接口测试场景是验证接口处理函数对请求的处理逻辑是否正确,我们可以借助httptest.NewRequest构造请求,httptest.NewRecorder记录响应结果,直接调用接口处理函数完成测试。
假设我们有一个简单的用户查询接口,处理逻辑如下:
package handler
import (
"encoding/json"
"net/http"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
// GetUser 处理获取用户信息的HTTP接口
func GetUser(w http.ResponseWriter, r *http.Request) {
// 只允许GET请求
if r.Method != http.MethodGet {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
// 模拟查询用户数据
user := User{ID: 1, Name: "张三"}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}
对应的测试代码可以这样编写:
package handler_test
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"your_project/handler"
)
func TestGetUser(t *testing.T) {
// 构造GET请求
req := httptest.NewRequest(http.MethodGet, "/user", nil)
// 创建响应记录器
w := httptest.NewRecorder()
// 调用接口处理函数
handler.GetUser(w, req)
// 验证响应状态码
if w.Code != http.StatusOK {
t.Errorf("期望状态码200,实际得到%d", w.Code)
}
// 验证响应头
contentType := w.Header().Get("Content-Type")
if contentType != "application/json" {
t.Errorf("期望Content-Type为application/json,实际得到%s", contentType)
}
// 解析响应体
var user handler.User
if err := json.NewDecoder(w.Body).Decode(&user); err != nil {
t.Fatalf("解析响应体失败:%v", err)
}
// 验证响应数据
if user.ID != 1 || user.Name != "张三" {
t.Errorf("期望用户ID为1,名字为张三,实际得到ID:%d,名字:%s", user.ID, user.Name)
}
}
测试完整服务:使用httptest.Server
如果需要测试的是已经注册到路由的完整HTTP服务,而不是单个处理函数,可以使用httptest.Server启动一个测试用的HTTP服务,它会监听一个随机的本地端口,测试完成后自动关闭。
假设我们使用net/http的标准路由注册接口:
package main
import (
"net/http"
)
func main() {
http.HandleFunc("/user", GetUser)
http.ListenAndServe(":8080", nil)
}
对应的测试代码可以这样编写:
package main_test
import (
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"testing"
)
func TestGetUserServer(t *testing.T) {
// 创建测试服务
ts := httptest.NewServer(http.HandlerFunc(GetUser))
defer ts.Close()
// 发送请求到测试服务
resp, err := http.Get(ts.URL + "/user")
if err != nil {
t.Fatalf("发送请求失败:%v", err)
}
defer resp.Body.Close()
// 验证状态码
if resp.StatusCode != http.StatusOK {
t.Errorf("期望状态码200,实际得到%d", resp.StatusCode)
}
// 读取响应体
body, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatalf("读取响应体失败:%v", err)
}
// 解析数据
var user User
if err := json.Unmarshal(body, &user); err != nil {
t.Fatalf("解析响应数据失败:%v", err)
}
// 验证数据
if user.ID != 1 || user.Name != "张三" {
t.Errorf("期望用户ID为1,名字为张三,实际得到ID:%d,名字:%s", user.ID, user.Name)
}
}
复杂场景测试技巧
模拟带参数的请求
如果需要测试带查询参数或者请求体的接口,可以在构造请求时添加对应参数,比如测试带查询参数的接口:
func TestGetUserWithQuery(t *testing.T) {
// 构造带查询参数的请求
req := httptest.NewRequest(http.MethodGet, "/user?id=1", nil)
w := httptest.NewRecorder()
GetUser(w, req)
// 后续验证逻辑
}
模拟POST请求
测试POST接口时,需要设置请求体和对应的Content-Type:
func TestCreateUser(t *testing.T) {
// 构造POST请求体
body := `{"name":"李四"}`
req := httptest.NewRequest(http.MethodPost, "/user", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
CreateUser(w, req)
// 后续验证逻辑
}
测试注意事项
- 测试过程中不要依赖外部真实服务,尽量使用
httptest工具模拟所有依赖,保证测试的独立性和可重复性。 - 每个测试用例只验证一个核心逻辑,避免单个测试用例过于复杂,方便定位问题。
- 对于需要验证错误场景的测试,比如参数错误、权限不足等,要单独编写测试用例覆盖这些场景。
- 测试完成后及时释放资源,比如关闭
httptest.Server、关闭响应体等,避免资源泄漏。