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
相关产品推荐
相关产品推荐

