How to mock a function calling io.Copy

Issue

Trying to mock the following function. It basically takes an object from S3 (io.ReadCloser) and writes it to a file that execution os.Open()d earlier (io.WriteCloser).

package main

import (
    "io"

    log "github.com/sirupsen/logrus"
)

func writeFile(destination io.WriteCloser, source io.ReadCloser) error {
    defer destination.Close()
    defer source.Close()
    _, err := io.Copy(destination, source)
    if err != nil {
        log.WithFields(log.Fields{"desc": "unable to copy contents from s3 to blahblah"}).Error(err)
        return err
    }
    return nil
}

I think I’m pretty close, but at the moment my test hangs and never errors / succeeds… I also realized that I can pass os.Stdout to my destination, but still ran into the same issue. Something is going on within io.Copy. I imagine it’s because I’m trying to copy empty data to nothing?

package main

import (
    "errors"
    "io"
    "reflect"
    "testing"
)

type mockReadCloser struct {}

func (m mockReadCloser) Read(p []byte) (int, error) { return 0, nil }
func (m mockReadCloser) Close() error               { return nil }

type mockWriteCloser struct{}

func (m mockWriteCloser) Close() error                      { return nil }
func (m mockWriteCloser) Write(b []byte) (n int, err error) { return 0, nil }

func Test_writeFile(t *testing.T) {
    type args struct {
        destination io.WriteCloser
        source      io.ReadCloser
    }
    tests := []struct {
        name    string
        args    args
        wantErr bool
    }{
        {
            name: "",
            args: args{
                destination: &mockWriteCloser{},
                source:      &mockReadCloser{},
            },
            wantErr: false,
        },
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            if err := writeFile(tt.args.destination, tt.args.source); (err != nil) != tt.wantErr {
                t.Errorf("writeFile() error = %v, wantErr %v", err, tt.wantErr)
            }
        })
    }
}

Solution

It’s because io.Copy doesn’t return until EOF or error.

Copy copies from src to dst until either EOF is reached on src or an error occurs. It returns the number of bytes copied and the first error encountered while copying, if any.

A successful Copy returns err == nil, not err == EOF. Because Copy is defined to read from src until EOF, it does not treat an EOF from Read as an error to be reported.

So, if you return EOF from your mockReadCloser.Read, it shouldn’t hang any more.

func (m mockReadCloser) Read(p []byte) (int, error) { return 0, io.EOF }

This is because Read is called repeatedly until there is nothing more to read (EOF).

for {
    nr, er := src.Read(buf)
    ...
}

Answered By – The Fool

Answer Checked By – Willingham (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.