go 中测试依赖 io.writer 的结构体时,若 mock 类型使用值接收器会导致状态更新失效;必须统一使用指针接收器并传入指针实例,才能使 write() 修改原始数据。
在 Go 单元测试中,为 io.Writer 接口编写 Mock 实现是常见需求(例如测试日志写入逻辑)。但一个典型陷阱是:误用值接收器(value receiver)定义 Write 方法,导致对内部字段(如 data []byte)的修改仅作用于副本,无法反映到原始实例上。
根本原因在于 Go 的一切皆按值传递。当 FileLogger.Log() 调用 this.File.Write(...) 时,如果 File 字段存储的是 WriterMock 值类型(而非指针),且 Write 方法又声明为值接收器:
func (w WriterMock) Write(b []byte) (n int, err error) { ... }那么每次调用都会将 w 作为 WriterMock 的一份拷贝传入——对 w.data 的追加操作只修改该副本,原始 WriterMock 实例的 data 字段保持不变,最终断言失败(得到空字符串)。
✅ 正确做法是:同时满足两个条件
修正后的完整代码如下:
// filelogger_test.go
type WriterMock struct {
data []byte
}
// ✅ 关键:指针接收器,确保可修改原始实例
func (w *WriterMock) Write(b []byte) (n int, err error) {
w.data = append(w.data, b...) // 修改的是原始 w.data
return len(b), nil
}
func NewMockedFileLogger() *FileLogger {
writer := &WriterMock{} // ✅ 传入指针
return &FileLogger{File: writer}
}
func TestLog(t *testing.T) {
fileLogger := NewMockedFileLogger()
fileLogger.Log("Hello World!")
// ✅ 类型断言也需匹配:*WriterMock
assert.Equal(t, "Hello World!\n", string(fileLogger.File.(*WriterMock).data))
}⚠️ 注意事项:
func TestLogWithBuffer(t *testing.T) {
var buf bytes.Bu
ffer
logger := &FileLogger{File: &buf}
logger.Log("Hello World!")
assert.Equal(t, "Hello World!\n", buf.String())
}总结:Go 接口实现的本质是方法集匹配,而方法集是否包含某方法,取决于接收器类型(T 和 *T 的方法集不同)。测试中务必确保 Mock 的接收器类型、实例传递方式与状态修改意图严格一致——这是写出可信赖单元测试的关键前提。