context 不可 mock,应使用 context.withcancel 等构造可控实例并显式调用 cancel() 测试取消路径,用 errors.is(err, context.canceled) 断言,避免 goroutine 泄漏。

Context本身不能Mock,只能构造和控制
Go 的 context.Context 是接口,但你**不该、也不能 mock 它**——标准库的 context 包不提供可替换实现,所有 mock 工具(gomock、gomonkey)对它无效。真正该做的,是用标准 context 构造函数生成可控实例,并在测试中主动触发取消或超时。
常见错误现象:mockgen -source=context.go 报错或生成空 mock;或者试图用 gomonkey.ApplyFunc(context.WithCancel, ...) 替换底层函数——这会破坏整个 context 树行为,且 Go 1.21+ 后这类 patch 更易失败。
- 正确做法:直接用
context.WithCancel、context.WithTimeout、context.WithValue创建测试所需上下文 - 被测函数若只接收
ctx context.Context参数,无需任何 mock,只需传入构造好的实例 - 若函数内部调用
http.Client.Do或db.QueryContext,它们天然响应 context,也无需 mock 这些客户端
测试取消路径必须显式调用 cancel()
用 context.Background() 或 context.TODO() 测试取消逻辑永远失败——它们不可取消。必须用 context.WithCancel 构造,并在测试中主动调用 cancel() 触发响应。
- 记得
defer cancel()防止 goroutine 泄漏,尤其在非取消分支里 - 断言取消是否生效,应检查返回错误是否为
context.Canceled,用errors.Is(err, context.Canceled),而非err == context.Canceled - 如果被测函数启动了 goroutine 并监听
ctx.Done(),测试中需加select { case
测试超时路径要设足够短的 deadline
用 context.WithTimeout(context.Background(), 10*time.Millisecond) 比设 5 秒更可靠——避免测试因环境波动变慢而偶然通过,掩盖实际未响应超时的问题。
- 超时后必须检查错误是否为
context.DeadlineExceeded,同样用errors.Is(err, context.DeadlineExceeded) - 若被测函数内部有阻塞调用(如未设 timeout 的
http.Get),它不会自动响应外部 context,此时测试需明确跳过或改用httptest.NewServer构建真实可中断的 endpoint - 不要在测试中 sleep 等待超时;让函数自己走到超时逻辑,然后立刻断言
测试 context.Value 要覆盖空值和非法键
如果函数依赖 ctx.Value(myKey),测试不能只传一个带值的 context,必须覆盖边界情况。
- 传
context.Background():验证函数是否有合理默认行为或明确报错 - 传
context.WithValue(context.Background(), "bad-key", "val"):确保类型断言安全,不 panic - 传
context.WithValue(context.Background(), myKey, nil):检查是否容忍 nil 值 - 键类型建议定义为未导出 struct,避免跨包冲突:
type ctxKey struct{},而非string
真正难的不是构造 context,而是判断哪些路径该测、哪些依赖其实根本不用 mock——比如标准库的 net/http 和 database/sql 对 context 的响应是确定的,强行 mock 反而增加维护成本。











