You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go语言中方法返回同类型实例时的可Mock接口编写问询

在Go中实现带自实例返回的可Mock Logger接口

针对Logger库开发中链式调用(返回自身实例)的Mock需求,以下是两种优雅的解决方案,避免你提到的三种问题:

方案1:接口方法返回自身接口类型,具体实现返回自身实例

这是最直接的解决方案,核心是让接口的链式方法统一返回接口本身,具体类型的方法返回接口类型并返回自身实例(因实例实现了接口,可隐式转换)。

定义Logger接口

所有链式方法返回Logger接口,确保接口的可Mock性:

type Logger interface {
    // 链式方法返回Logger接口
    WithFields(fields map[string]interface{}) Logger
    WithContext(ctx context.Context) Logger
    // 核心日志输出方法
    Info(msg string)
    Error(msg string, err error)
}

具体实现(以Zap为例)

具体类型的方法返回接口类型,内部构造新实例并返回:

type ZapLogger struct {
    inner *zap.Logger
}

func (z *ZapLogger) WithFields(fields map[string]interface{}) Logger {
    fieldsZap := make([]zap.Field, 0, len(fields))
    for k, v := range fields {
        fieldsZap = append(fieldsZap, zap.Any(k, v))
    }
    newInner := z.inner.With(fieldsZap...)
    return &ZapLogger{inner: newInner}
}

func (z *ZapLogger) WithContext(ctx context.Context) Logger {
    return &ZapLogger{inner: z.inner.With(zap.Any("trace_id", ctx.Value("trace_id")))}
}

func (z *ZapLogger) Info(msg string) {
    z.inner.Info(msg)
}

func (z *ZapLogger) Error(msg string, err error) {
    z.inner.Error(msg, zap.Error(err))
}

Mock实现

Mock类型完全独立,只需实现Logger接口即可,链式方法返回自身:

type MockLogger struct {
    // 用于验证调用的状态字段
    WithFieldsCalls int
    LastInfoMsg     string
    LastErrorErr    error
}

func (m *MockLogger) WithFields(fields map[string]interface{}) Logger {
    m.WithFieldsCalls++
    return m // 返回Mock自身,满足接口返回要求
}

func (m *MockLogger) WithContext(ctx context.Context) Logger {
    return m
}

func (m *MockLogger) Info(msg string) {
    m.LastInfoMsg = msg
}

func (m *MockLogger) Error(msg string, err error) {
    m.LastErrorErr = err
}

这种方案的优势:

  • 保留链式调用的优雅语义,无需改为突变模式
  • Mock实现完全独立,不受具体Logger类型限制
  • 编译合法,因具体方法返回值与接口方法定义一致

方案2:接口拆分+泛型(适需保留具体类型特性的场景)

如果业务代码偶尔需要访问具体Logger的特殊方法,可以用泛型接口兼顾Mock和类型安全:

// 泛型Logger接口,链式方法返回具体类型
type Logger[T any] interface {
    WithFields(fields map[string]interface{}) T
    Info(msg string)
    Error(msg string, err error)
}

// 具体Zap实现
type ZapLogger struct {
    inner *zap.Logger
}

func (z *ZapLogger) WithFields(fields map[string]interface{}) *ZapLogger {
    fieldsZap := make([]zap.Field, 0, len(fields))
    for k, v := range fields {
        fieldsZap = append(fieldsZap, zap.Any(k, v))
    }
    return &ZapLogger{inner: z.inner.With(fieldsZap...)}
}

func (z *ZapLogger) Info(msg string) { z.inner.Info(msg) }
func (z *ZapLogger) Error(msg string, err error) { z.inner.Error(msg, zap.Error(err)) }

// Mock实现
type MockLogger struct {
    WithFieldsInvoked bool
}

func (m *MockLogger) WithFields(fields map[string]interface{}) *MockLogger {
    m.WithFieldsInvoked = true
    return m
}

func (m *MockLogger) Info(msg string) {}
func (m *MockLogger) Error(msg string, err error) {}

业务代码中可通过Logger[*ZapLogger]或Logger[*MockLogger]声明变量,既保留链式调用的类型特性,又能自由替换Mock实现。

内容的提问来源于stack exchange,提问作者Vaishnav Sreekanth Menon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 02:52:49