Go中用httptest.Server模拟HTTP服务可实现可靠、快速、稳定的单元测试,通过内存服务器验证请求参数、响应状态码与Body,需defer server.Close()防泄漏,并结合ServeMux、超时控制、错误响应等覆盖边界场景。

Go中用httptest.Server模拟HTTP服务
直接调用真实API做单元测试不可靠、慢、易失败,Go标准库提供httptest.Server在内存中启动一个临时HTTP服务,专为测试设计。
- 它返回一个运行中的
*httptest.Server,其URL字段可直接用于http.Get或http.Client.Do - 必须在测试结束前调用
server.Close(),否则端口不释放、goroutine泄漏 - 适合验证请求是否发出、参数是否正确、响应状态码/Body是否符合预期
func TestMyClient(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/api/data" {
t.Errorf("expected /api/data, got %s", r.URL.Path)
}
if r.Method != "GET" {
t.Errorf("expected GET, got %s", r.Method)
}
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"id":123}`))
}))
defer server.Close() // 关键:必须defer
client := &http.Client{}
resp, err := client.Get(server.URL + "/api/data")
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("expected 200, got %d", resp.StatusCode)
}}
用http.ServeMux或http.Handler控制路由行为
单个httptest.Server默认只支持一个http.HandlerFunc。若需多路径(如/health和/users),应使用http.ServeMux或自定义http.Handler。
-
http.ServeMux支持Handle/HandleFunc注册多个路由,更贴近真实服务器结构 - 避免在
HandlerFunc里硬编码逻辑分支(如if r.URL.Path == ...),不易维护且难覆盖边界情况 - 注意:
ServeMux不支持通配符或正则,复杂路由需用第三方库(如gorilla/mux)或自行实现ServeHTTP
func TestMultiPathClient(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
})
mux.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
w.Write([]byte(`[{"id":1,"name":"alice"}]`))
})
server := httptest.NewUnstartedServer(mux)
server.Start()
defer server.Close()
// 测试 /health
resp, _ := http.Get(server.URL + "/health")
// 测试 /users
resp2, _ := http.Post(server.URL+"/users", "application/json", nil)
}
测试超时、重试、错误响应等边界场景
真实网络请求会遇到连接拒绝、超时、5xx响应等,仅测200 OK远远不够。关键在于控制http.Client行为,而非依赖服务端返回。
家电小商城网站源码1.0
家电公司网站源码是一个以米拓为核心进行开发的家电商城网站模板,程序采用metinfo5.3.9 UTF8进行编码,软件包含完整栏目与数据。安装方法:解压上传到空间,访问域名进行安装,安装好后,到后台-安全与效率-数据备份还原,恢复好数据后到设置-基本信息和外观-电脑把网站名称什么的改为自己的即可。默认后台账号:admin 密码:132456注意:如本地测试中127.0.0.1无法正常使用,请换成l
下载
- 用
&http.Client{Timeout: 10 * time.Millisecond}触发超时,验证客户端是否妥善处理context.DeadlineExceeded - 用
httptest.NewUnstartedServer启动后不调用Start(),再发起请求,可模拟“连接被拒绝” - 在Handler中写
http.Error(w, "bad request", http.StatusBadRequest),测试客户端对非2xx的容错逻辑 - 不要用
time.Sleep等待,而应通过server.Close()后立即发请求来制造失败
func TestClientTimeout(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(100 * time.Millisecond) // 故意延迟
w.WriteHeader(http.StatusOK)
}))
defer server.Close()
client := &http.Client{
Timeout: 50 * time.Millisecond,
}
_, err := client.Get(server.URL + "/slow")
if !errors.Is(err, context.DeadlineExceeded) {
t.Fatalf("expected timeout error, got %v", err)
}}
避免常见陷阱:全局变量、未关闭Body、忽略重定向
测试中容易因疏忽引入隐性失败,这些点高频出问题且难定位。
- 别在测试函数外定义
http.Client或复用httptest.Server——并发测试会冲突,每个测试应独占资源 - 每次
resp.Body必须Close(),否则文件描述符泄漏,大量测试后可能报too many open files -
http.DefaultClient默认启用重定向(CheckRedirect),若被测逻辑依赖302跳转但没显式处理,测试可能意外成功或失败;建议显式构造Client并设CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse } - 用
io.ReadAll(resp.Body)读取完整Body,别只读前几个字节,否则后续读取为空
测试HTTP客户端的核心不是“能不能通”,而是“行为是否可控、边界是否覆盖、资源是否干净”。真正难的从来不是起一个test server,而是让每一个http.Client配置、每一条错误路径、每一次Body读取都落在你的掌控之中。









