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?
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.
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)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With