Go中如何模拟错误进行测试_Go错误注入方法

最直接的错误模拟方式是用 errors.New 或 fmt.Errorf 创建可控错误,适用于简单判断场景;推荐通过接口替换实现 mock 注入,确保上下文错误使用标准值,并用 errors.Is/errors.As 断言。

errors.Newfmt.Errorf 构造可控错误

最直接的错误模拟方式是手动创建错误值,适用于被测函数内部不依赖具体错误类型、只做 err != nil 判断或简单字符串匹配的场景。注意不要在测试中用 errors.New("something went wrong") 这类模糊描述,而应使用有辨识度的字符串,方便断言定位。

  • 若被测逻辑用 errors.Is 判断底层错误,需确保注入的错误实现了嵌套(如用 fmt.Errorf("wrap: %w", err)
  • 若用 errors.As 提取自定义错误类型,则必须传入对应类型的指针实例,不能只传 errors.New
  • 避免在测试里重复定义错误变量;可统一放在 var 块中,例如:
    var (
        ErrNotFound = errors.New("not found")
        ErrTimeout  = fmt.Errorf("timeout: %w", context.DeadlineExceeded)
    )

通过接口替换实现错误注入(推荐)

当被测函数依赖外部服务(如数据库、HTTP 客户端),应将依赖抽象为接口,并在测试时传入返回预设错误的 mock 实现。这是 Go 测试中最可靠、解耦最彻底的方式。

  • 不要修改原函数签名强行加参数;而是提取依赖为字段或构造函数参数,例如:
    type Service struct {
        db DBClient
    }
    func NewService(db DBClient) *Service { return &Service{db: db} }
  • mock 实现只需满足接口方法签名,错误返回写死即可:
    type mockDB struct{}
    func (m mockDB) Query(ctx context.Context, sql string) (*Rows, error) {
        return nil, errors.New("simulated db failure")
    }
  • 注意上下文取消错误(context.Canceled / context.DeadlineExceeded)需用标准值,否则 errors.Is(err, context.Canceled) 会失败

用函数字段替代硬编码调用

对无法轻易抽成接口的小型依赖(比如一个工具函数),可将其声明为结构体字段或包级变量,并在测试前替换为返回错

误的闭包。

  • 包级变量方式需配合 init 或构造函数恢复默认值,防止测试污染:
    var timeNow = time.Now
    func TestSomething(t *testing.T) {
        defer func() { timeNow = time.Now }()
        timeNow = func() time.Time { return time.Unix(0, 0) }
        // ...
    }
  • 结构体字段更安全,但需确保被测逻辑确实通过该字段调用:
    type Processor struct {
        nowFunc func() time.Time
    }
    func (p *Processor) Process() error {
        if p.nowFunc().After(deadline) {
            return errors.New("too late")
        }
        return nil
    }
  • 慎用 unsafe 或反射篡改私有函数——这会让测试脆弱且难以维护

注意错误比较方式与测试断言粒度

Go 中错误不是值类型,直接用 == 比较仅适用于导出的包级错误变量或 errors.New 返回的同一实例。多数情况应使用 errors.Iserrors.As,否则测试容易误判。

  • 如果被测函数返回的是 fmt.Errorf("failed: %w", originalErr),则断言必须用 errors.Is(got, expected),而非 got == expected
  • 避免只检查错误字符串包含某关键词(strings.Contains(err.Error(), "timeout")),这属于脆弱断言;优先走类型或语义判断
  • 对 HTTP handler 类测试,注意错误是否被中间件捕获并转为响应状态码——此时要检查响应体/状态码,而非原始 error 值
真实项目里最容易忽略的是上下文错误的构造方式和接口 mock 的完备性:很多人只 mock 成功路径,漏掉所有可能的失败分支;还有人用字符串比较代替 errors.Is,导致底层错误包装变化后测试无声失效。