如何使用Golang进行接口测试_Golang mock接口与testing实践

10次阅读

httptest 可免启动真实服务测试 HTTP handler:NewRecorder 适合单元测试 handler 逻辑,NewServer 适合测试客户端;gomock 要基于 Interface mock 依赖;中间件和时间/随机数需抽象为可注入依赖以保证测试稳定。

如何使用Golang进行接口测试_Golang mock接口与testing实践

net/http/httptest 快速 mock HTTP handler 测试接口逻辑

不需要启动真实服务,httptest.NewServerhttptest.NewRecorder 就能验证路由状态码、响应体。关键在于把 handler 当作普通函数调用,不依赖网络。

  • httptest.NewRecorder() 用于捕获响应(ResponseWriter),适合单元测试 handler 函数本身
  • httptest.NewServer(http.Handler) 启动临时服务,适合测试客户端代码(比如你写的 http.Client 调用)
  • 别直接传 nilhttp.ServeHTTP —— 它会 panic;务必用 httptest.NewRecorder() 构造有效的 http.ResponseWriter
func TestUserHandler(t *testing.T) { 	req, _ := http.NewRequest("GET", "/user/123", nil) 	rr := httptest.NewRecorder() 	handler := http.HandlerFunc(userHandler) // 假设 userHandler 是你的 http.HandlerFunc 	handler.ServeHTTP(rr, req)  	if rr.Code != http.StatusOK { 		t.Errorf("expected status OK, got %d", rr.Code) 	} 	if !strings.Contains(rr.Body.String(), `"id":123`) { 		t.Error("response body doesn't contain expected jsON") 	} }

gomock + mockgen 替换依赖的 interface(如数据库、第三方 SDK)

Go 没有运行时反射 mock,必须基于 interface。如果你的 handler 依赖了 UserService,那它必须是接口类型,且方法可被 gomock 生成 mock 实现。

  • 不能对 Struct 或具体类型 mock —— mockgen 只接受 interface{} 作为输入
  • 生成命令示例:mockgen -source=service.go -destination=mocks/mock_service.go -package=mocks
  • 使用时需手动调用 mockCtrl.Finish(),否则 expect 未满足会 panic(常见漏写点)
  • 避免在测试中 new 出真实 service 实例再赋值给 interface 字段——这等于没 mock
func TestCreateUserWithMockDB(t *testing.T) { 	ctrl := gomock.NewController(t) 	defer ctrl.Finish()  	mockRepo := mocks.NewMockUserRepository(ctrl) 	mockRepo.EXPECT().Create(gomock.Any()).Return(int64(99), nil)  	svc := &UserService{repo: mockRepo} 	_, err := svc.Create(&User{Name: "alice"}) 	if err != nil { 		t.Fatal(err) 	} }

测试带中间件的 handler(如 JWT 验证、日志)时如何绕过或控制依赖行为

中间件通常包装原始 handler,测试时若不处理其依赖(如 jwt.Parselog.printf),会导致测试不可控或失败。

  • 把外部依赖抽象成字段或选项参数,例如 type AuthMiddleware struct { Parser TokenParser },测试时注入返回固定 user 的 mock TokenParser
  • 不要在中间件里硬编码 os.Stdoutlog.default() —— 改为接收 io.Writer*log.Logger,测试时传 io.Discard
  • 如果中间件依赖 http.Request.Context() 中的值(如 ctx.Value("user")),用 req = req.WithContext(context.WithValue(req.Context(), key, value)) 注入

避免在测试中误用 time.Now()rand.Intn() 导致结果不稳定

这类函数让测试变成「概率性通过」,尤其在并发测试或 CI 环境下容易偶发失败。

立即学习go语言免费学习笔记(深入)”;

  • 将时间/随机数生成器作为依赖传入,例如 type Clock interface { Now() time.Time },测试时用固定返回值的实现
  • 不要在 handler 内直接调用 time.Now().unix() —— 提取为可注入的方法或函数变量(如 var nowFunc = time.Now),测试前替换
  • 同理,math/rand封装为 interface 或接受 *rand.Rand 实例,避免全局 rand.Seed 影响其他测试

真正难的不是写 mock,而是设计出可测的接口边界——比如一个 handler 里混着解析 json、查 DB、发邮件、写日志,那就得先拆。

text=ZqhQzanResources