手动 Mock 最轻量但需自管验证;testify/mock 适合中型项目,语法直观但性能损耗大、并发易出错。

手动实现 Mock 接口最轻量,但得自己管调用验证
Go 没法直接 patch 结构体方法,所以必须先定义接口(比如 UserRepository),再写个结构体去实现它。这是所有 Mock 的起点,也是最可控的方式。
常见错误是忘记处理 nil 返回或漏实现某个方法,导致测试 panic;更隐蔽的坑是 mock 行为写死在结构体字段里,没法按不同测试用例动态切换。
- 适合小项目、单个接口、逻辑简单场景
- 不依赖第三方库,零额外编译开销
- 无法自动校验“是否被调用”“调用了几次”,得靠自己加计数器或布尔标记
- 示例中用闭包赋值
mockGetUser函数,比固定字段更灵活
testify/mock 适合中型项目,写法直观但性能有损耗
它靠 mock.Mock 基类 + Called()/On().Return() 实现行为录制与回放,语法接近行为驱动风格,上手快。
容易踩的坑:反射调用带来明显性能开销(基准测试显示比手动 stub 慢 20–40 倍),并发测试时可能因共享 controller 状态出问题;另外 AssertExpectations(t) 忘写会导致 mock 调用未被检查,测试“假通过”。
立即学习“go语言免费学习笔记(深入)”;
- 推荐用于接口方法不多(
- 支持参数匹配、多次调用不同返回值(
Times(2).Return(...)) - 不生成额外代码文件,调试时堆栈清晰
GoMock(mockgen)适合大型项目,强类型安全但学习成本高
它用 mockgen 工具从接口定义自动生成 Mock 类型,配合 gomock.Controller 做严格模式控制,是目前企业级 Go 项目的事实标准。
典型问题包括:生成代码体积大(平均是源接口 3 倍)、EXPECT() 写错参数类型会编译失败、忘记 defer ctrl.Finish() 导致未验证期望时测试静默通过。
- 必须用接口,且不能含非导出方法(否则 mockgen 报错)
- 支持调用顺序校验、参数深度匹配(如
Eq(&User{ID:1}))、延迟返回等高级特性 - CI 中需确保
mockgen版本与项目 Go 版本兼容,否则生成代码可能 panic
别忽略:HTTP/DB 这类外部依赖该用专用 Mock 库
想 mock http.Get 或 db.QueryRow?硬套上面三类接口 Mock 往往绕远路。真实项目里,sqlmock 拦 SQL、redismock 拦 Redis 命令、mockey 拦任意函数(包括 time.Now),才是高效解法。
比如用 mockey.Mock(time.Now).Return(customTime) 替换时间函数,比抽象出 Clock 接口再 mock 简洁得多——前提是你的测试不介意使用运行时打桩技术。
复杂点在于:这些库彼此不兼容,选一个就要接受它的约束;比如 sqlmock 要求你用 *sql.DB,就不能同时用 pgx 原生连接。










