Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How would I mock a call to ioutil.ReadFile in go?

Tags:

testing

go

I have the following function:

func ObtainTranslationStringsFile(path string) ([]string, error) {
    if contents, err := ioutil.ReadFile(path); err != nil {
        return ObtainTranslationStrings(string(contents))
    } else {
        return nil, err
    }
}

I need to mock ioutil.ReadFile, but I'm not sure how to do it. Is it possible?

like image 353
Geo Avatar asked Jan 04 '14 17:01

Geo


People also ask

What is stub in Golang?

stub is replacement for some dependency in your code that will be used during test execution. It is typically built for one particular test and unlikely can be reused for another because it has hardcoded expectations and assumptions. mock takes stubs to next level.


2 Answers

In case you need to mock an io.Reader here is a solution using https://github.com/stretchr/testify

In your package declare

type ioReader interface {
    io.Reader
}

This is only needed to tel mockery that you need such interface an it will generate the corresponding mock.

Then generate mocks

go get github.com/vektra/mockery/.../
mockery -inpkg -all

Then in your test code you can do that

str := "some string"
r := &mockIoReader{}
r.
    On("Read", mock.Anything).
    Run(func(args mock.Arguments) {
        bytes := args[0].([]byte)
        copy(bytes[:], str)
    }).
    Return(len(str), nil)
like image 86
sbouba Avatar answered Oct 10 '22 06:10

sbouba


There are a couple of ways to handle this if you want to mock this. The first, and perhaps simplest, is to change from using ioutil.ReadFile, and instead call ioutil.ReadAll which takes an io.Reader interface. It's pretty easy to then inject your own io.Reader/filesystem implementation per this method.

If you prefer not to change the method signature, the other option is to not use ioutil, and instead declare a function replacement for ReadFile. Injecting that would be easier with an object method rather than a function, but it's do-able. It just relies on package level variable magic that may seem distasteful to some. See the options explored below:

package main

import (
    "bytes"
    "fmt"
    "io"
    "io/ioutil"
    _ "log"
)

type ReadFile func(filename string) ([]byte, error)
// myReadFile is a function variable that can be reassigned to handle mocking in option #2
var myReadFile = ioutil.ReadFile

type FakeReadFiler struct {
    Str string
}

// here's a fake ReadFile method that matches the signature of ioutil.ReadFile
func (f FakeReadFiler) ReadFile(filename string) ([]byte, error) {
    buf := bytes.NewBufferString(f.Str)
    return ioutil.ReadAll(buf)
}

func main() {
    payload := "PAYLOAD"
    path := "/dev/nul"
    buf := bytes.NewBufferString(payload)

    // option 1 is more elegant, but you have to change the method signature to take an io.Reader
    result1, err := ObtainTranslationStringsFileChoice1(buf)
    fmt.Printf("ObtainTranslationStringsFileChoice1 == %#v, %#v\n", result1, err)

    // option 2 keeps the method signature, but allows you to reassign a the myReadFile variable to change the method behavior for testing
    fake := FakeReadFiler{Str: payload}
    myReadFile = fake.ReadFile
    result2, err := ObtainTranslationStringsFileChoice2(path)
    fmt.Printf("ObtainTranslationStringsFileChoice2 == %#v, %#v\n", result2, err)
}

func ObtainTranslationStringsFileChoice1(rdr io.Reader) ([]string, error) {
    if contents, err := ioutil.ReadAll(rdr); err == nil {
        return []string{string(contents)}, nil
    } else {
        return nil, err
    }
}

func ObtainTranslationStringsFileChoice2(path string) ([]string, error) {
    if contents, err := myReadFile(path); err == nil {
        return []string{string(contents)}, nil
    } else {
        return nil, err
    }
}

Playground versions here: sandbox.

If you want to get more sophisticated, I recommend making a full on file system mock. It's what I typically do, and not as difficult as it sounds: https://talks.golang.org/2012/10things.slide#8. With your example, you weren't using structs and interfaces, which really makes this sort of mocking much more robust.

like image 25
mattmc3 Avatar answered Oct 10 '22 06:10

mattmc3