How would I mock a call to ioutil.ReadFile in go?
Solution 1
Don't mock ioutil.ReadFile
. Once you mocked it, testing ObtainTranslationStringsFile
wouldn't test any more than testing ObtainTranslationStrings
would. If you need to test ObtainTranslationStringsFile
, create a temporary file and pass its path to ObtainTranslationStringsFile
. That way you will actually test the functionality that is added by that function.
Solution 2
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.
Solution 3
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)
![MarkD](https://i.stack.imgur.com/yQ5nj.jpg?s=256&g=1)
MarkD
Updated on July 24, 2022Comments
-
MarkD almost 2 years
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?
-
Dhir Pratap almost 7 yearsI disagree. A test for
ObtainTranslationStringsFile
should not try exerciseioutil.ReadFile
, it should exercise howpath
,contents
,err
, and the return value are handled. Thusioutil.ReadFile
can and should be mocked out. (And exercised only at integration test level) -
icelemon about 6 yearsOne shouldn't need to exercise the "real ioutils.ReadFile" function to do unit test. The unit test for the function ObtainTranslationStringsFile should only testing the logic inside the function, but not the function it's calling. Therefore, if there is way, it's always preferred to mock ioutils.ReadFile to return error, and to not return error.
-
Mark G. almost 6 years“Testing
ObtainTranslationStringsFile
wouldn't test any more than testingObtainTranslationStrings
would.” Not so. There’s logic in the former that’s not present in the latter — namely, what happens whenerr == nil
vs.err != nil
. That logic warrants its own testing. Unit tests forObtainTranslationStringsFile
should not assert/provoke “correct” behavior of/fromioutil.ReadFile
. That’s not their responsibility. Their job is to assert correct behavior ofObtainTranslationStringsFile
in response to (a mocked)ioutil.ReadFile
. Of course, integration tests are another story…