Mocking os.Open and related calls for more code coverage

2,453 views
Skip to first unread message

obou...@mirantis.com

unread,
Sep 27, 2016, 12:37:36 PM9/27/16
to golang-nuts
Hello

may be it is a trivial question but I could not find any usable answer to the following requirement therefore I thought that
surely some of you have the answer

Let's assume I have some code like follows:

func (obj *myObjStruct) MyFn(fsPath string) (myRetType, error) {
    cpath := path.Join(fsPath, "subdir", "input.txt")
    fh, err := os.Open(cpath)
    if err != nil {
        log.Error(fmt.Sprintf("Got error %#v", err))
        return nil, err
    }
    defer fh.Close()
    scanner := bufio.NewScanner(fh)
    for scanner.Scan() {
        inLine := scanner.Text()
        ...

 

How should I proceed to mock the os.Open and scanner.xxx calls so that I can exercise the remaining parts of the code (... above)

May be I need to rewrite this function a little bit before being able to do so but I obviously would not agree to lose the benefits of defering
fh.Close() call

Thanks for any advice/pointer/...

Edward Muller

unread,
Sep 27, 2016, 2:33:17 PM9/27/16
to obou...@mirantis.com, golang-nuts
One option is to adjust the method to take an io.ReadCloser instead of a path so you could pass in anything that supported the io.ReadCloser interface (which is easily adapted to via ioutil.NoCloser(io.Reader)). 

Another option is to generate a temporary file with data you want to test with and pass that in.

Which one to use (or both for that matter) depend on many external factors.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Konstantin Khomoutov

unread,
Sep 27, 2016, 3:03:10 PM9/27/16
to Edward Muller, obou...@mirantis.com, golang-nuts
On Tue, 27 Sep 2016 18:30:04 +0000
Edward Muller <edwa...@interlix.com> wrote:

> One option is to adjust the method to take an io.ReadCloser instead
> of a path so you could pass in anything that supported the
> io.ReadCloser interface (which is easily adapted to via
> ioutil.NoCloser(io.Reader)).

A minor typo correction: that's ioutil.NopCloser (with "Nop" supposedly
standing for "No Operation").

[...]

Nate Finch

unread,
Sep 28, 2016, 9:11:26 AM9/28/16
to golang-nuts
freeformz had a good suggestion to try to make your function take an io.Reader, which is good not just for testing, but for future flexibility... but of course, you still need some place that opens the file... that's inescapable.  The way I test those sorts of things is to use a variable to hold the function variable, and swap that out during testing:


The article I wrote suggests a global variable, but that restricts you from doing parallel tests (and global variables are generally bad)... you could also make it a variable on the struct that has the method you're testing, then it's isolated from other tests and values... i.e.

type myObjStruct struct {
    ...
    OpenFile func(string) (*os.File, error)
}

then when you construct the type, in production you do myObj.OpenFile = os.Open  and in testing you do myObj.OpenFile = testOpen (or whatever)

Eric Johnson

unread,
Sep 28, 2016, 10:48:58 PM9/28/16
to golang-nuts
Why not, instead of passing a file path as a parameter, pass a function that returns a ReadCloser (and error, of course). That way, you can preserve the existing semantics of your function, but move the decision of what to open, how to open, and how to handle the error for when it fails - all out of your function.

Or just use a temp file.

Eric

Reply all
Reply to author
Forward
0 new messages