user2184212
user2184212

Reputation: 19

How do you mock calls on structs (e.g. csv.Writer) in Go?

Here is some code

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(file[0])
  header, err := reader.Read()
  if err != nil {
    return nil, err
  }
  if err := writer.Write(header); err != nil {
    return nil, err
  }
// more code...
}

How would I write a test case for when writer.Write() fails? I'm finding it difficult to mock calls on structs that do not implement an interface (csv.Writer).

I've tried creating a separate interface:

type CSVWriter interface {
  Write(record []string) error
  Flush()
  Error() error
}

and mocking it with testify/mock

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()
    return
}
func (m *MockCSVWriter) Error() error {
    args := m.Called()
    return args.Error(0)
}

but this chunk of code doesn't mock the writer.Write() call

var mockCSVWriter MockCSVWriter
defer mockCSVWriter.AssertExpectations(t)
mockCSVWriter.On("Write", mock.Anything).Return(expectedError).Once()

Any suggestions would be greatly appreciated!

Upvotes: 0

Views: 410

Answers (1)

Sapan Vashishth
Sapan Vashishth

Reputation: 303

MockCSVWriter is not being sent to the CsvCombiner and new one is getting created always at writer := csv.NewWriter(combinedCSV).

Have a field in CsvCombiner to hold the CSVWriter, which can be replaced with the mocked one. type to be of interface that csv.Writer and MockCSVWriter both satisfy

Better approach will be to Mock interface io.Writer that is supplied to csv.NewWriter instead and pass this as a field in CsvCombiner struct.

type CsvCombiner struct {
  hasHeader bool
  writer io.Writer // This will be "injected" with mock
}

func (c *CsvCombiner) Combine() (io.Reader, error) {
    writer := csv.NewWriter(c.writer) // Use struct field io.writer

    header := []string{"sada", "sasa"}
    if err := writer.Write(header); err != nil {
        return nil, err
    }
// more code...
}

Mock interface and usage

// A mock of io.Writer
type MockIOWriter struct {
    mock.Mock
}

func (mw *MockIOWriter) Write(p []byte) (n int, err error) {
    args := mw.Called(p)
    return 1, args.Error(0)
}

// Mock usage
  mockIOWriter := &MockIOWriter{}
  mockIOWriter.On("Write", mock.Anything).Return(expectedError).Once()

  wrt := &CsvCombiner{writer: mockIOWriter}
  wrt.Combine()

Also, write error can be thrown during csv Writer methods Write() and/or Flush() so check with writer.Error() for getting the error

Upvotes: 0

Related Questions