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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 09:35:41