I recently rewrote a piece of software to practice unit-testing. I decided to constrain myself and do dependency-free code, so I can do better and easier tests. I came to the following solution while writing the methods that deal with files :
/* foo.go */
package main
import (
"io"
"os"
)
type Foo struct {
reader io.Reader
}
func NewFoo(filename string) (*Foo, error) {
return newFoo(filename, os.Open)
}
type opener func(string) (*os.File, error)
func newFoo(filename string, open opener) (*Foo, error) {
file, err := open(filename)
if err != nil {
return nil, err
}
return &Foo{
reader: file,
}, nil
}
And here is the test file that come along.
/* foo_test.go */
package main
import (
"errors"
"os"
"testing"
)
func TestNewFooOpeningError(t *testing.T) {
f, err := newFoo("", func(_ string) (*os.File, error) {
return nil, errors.New("openning error")
})
if f != nil {
t.Error(f, "!=", nil)
}
if err.Error() != "openning error" {
t.Error(err.Error(), "!=", "openning error")
}
}
func TestNewFooOK(t *testing.T) {
f, err := newFoo("", func(_ string) (*os.File, error) {
return nil, nil
})
if f == nil {
t.Error(f, "==", nil)
}
if err != nil {
t.Error(err.Error(), "!=", nil)
}
}
It looks ok for me, but when there is more than one method to fake, I feel it a bit messy/ugly/unreadable/"pick your own adjective"
My questions are the following :
- Is this pattern sane ?
- If not, do you have any saner alternative ?
- Do you recommend a particular testing good practice for this cases ?