Go中如何Mock结构体(如csv.Writer)?测试Write失败场景求教
如何测试csv.Writer.Write()调用失败的场景
问题背景
现有如下Go代码,需要为CsvCombiner.Combine方法中writer.Write(header)调用失败的场景编写测试用例,但直接使用csv.Writer无法Mock:
type Combiner interface { Combine(files []io.Reader) (io.Reader, error) } type CsvCombiner struct { hasHeader bool } func (c *CsvCombiner) Combine(files []io.Reader) (io.Reader, error) { combinedCSV := &bytes.Buffer{} writer := csv.NewWriter(combinedCSV) reader := csv.NewReader(files[0]) header, err := reader.Read() if err != nil { return nil, err } if err := writer.Write(header); err != nil { return nil, err } // more code... }
尝试定义了CSVWriter接口并使用testify/mock创建Mock,但无法成功MockWrite调用。
解决方案:依赖注入Mock Writer
核心思路是让CsvCombiner依赖自定义的CSVWriter接口,通过依赖注入替换掉直接创建csv.Writer的硬编码逻辑,从而在测试时注入Mock实现。
步骤1:重构CsvCombiner,添加Writer创建依赖
修改CsvCombiner结构,增加一个用于创建CSVWriter的工厂函数字段,保持原接口签名兼容的同时,实现可替换的Writer创建逻辑:
// 保留你定义的CSVWriter接口,对齐csv.Writer核心方法 type CSVWriter interface { Write(record []string) error Flush() Error() error } // 定义Writer工厂函数类型,用于生成CSVWriter实例 type CSVWriterFactory func(w io.Writer) CSVWriter // 修改CsvCombiner结构体,添加工厂字段 type CsvCombiner struct { hasHeader bool writerFactory CSVWriterFactory } // 提供默认构造函数,生产环境使用真实的csv.Writer func NewCsvCombiner(hasHeader bool) *CsvCombiner { return &CsvCombiner{ hasHeader: hasHeader, writerFactory: func(w io.Writer) CSVWriter { return csv.NewWriter(w) }, } } // 重构Combine方法,通过工厂获取Writer实例 func (c *CsvCombiner) Combine(files []io.Reader) (io.Reader, error) { combinedCSV := &bytes.Buffer{} // 使用工厂创建Writer,而非直接调用csv.NewWriter writer := c.writerFactory(combinedCSV) reader := csv.NewReader(files[0]) header, err := reader.Read() if err != nil { return nil, err } if err := writer.Write(header); err != nil { return nil, err } // 补充原代码中可能遗漏的Flush和Error检查 writer.Flush() if err := writer.Error(); err != nil { return nil, err } return combinedCSV, nil }
步骤2:编写测试用例,注入Mock Writer
使用你之前定义的MockCSVWriter,在测试时替换CsvCombiner的writerFactory为返回Mock实例的函数:
import ( "bytes" "errors" "testing" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/assert" ) // 你的MockCSVWriter实现(保持不变) type MockCSVWriter struct { mock.Mock } func (m *MockCSVWriter) Write(record []string) error { args := m.Called(record) return args.Error(0) } func (m *MockCSVWriter) Flush() { m.Called() } func (m *MockCSVWriter) Error() error { args := m.Called() return args.Error(0) } // 测试Write调用失败的场景 func TestCsvCombiner_Combine_WriteFailed(t *testing.T) { // 准备测试用的输入文件内容 testFile := bytes.NewBufferString("col1,col2\nval1,val2") // 定义预期返回的错误 expectedErr := errors.New("write failed") // 创建Mock Writer实例 mockWriter := new(MockCSVWriter) defer mockWriter.AssertExpectations(t) // 设置Mock预期:调用Write时返回预设错误 mockWriter.On("Write", mock.Anything).Return(expectedErr).Once() // 根据Combine方法逻辑,补充Flush和Error的调用预期 mockWriter.On("Flush").Once() mockWriter.On("Error").Return(nil).Once() // 创建CsvCombiner实例,注入返回Mock的工厂函数 combiner := &CsvCombiner{ hasHeader: true, writerFactory: func(w io.Writer) CSVWriter { return mockWriter }, } // 执行测试方法 _, err := combiner.Combine([]io.Reader{testFile}) // 断言返回的错误与预期一致 assert.ErrorIs(t, err, expectedErr) }
关键说明
- 依赖注入是Go中测试外部依赖组件的标准做法,通过抽象接口+工厂函数,实现生产代码用真实实现、测试代码用Mock实现的分离。
- 测试时需要覆盖
Combine方法中所有调用的CSVWriter方法(如Flush和Error),否则AssertExpectations会因未满足预期而报错。 - 如果不想修改
CsvCombiner结构体,也可以将writerFactory作为Combine方法的参数,但会改变原接口签名,需根据实际场景权衡。
内容的提问来源于stack exchange,提问作者user2184212
相关产品推荐
相关产品推荐

