Let's suppose I have a function which return base64
encoded string for a file which is located at a specific path
.
func getFile(path string) (string, error) {
imgFile, err := ioutil.ReadFile(path)
if err != nil {
return "", fmt.Errorf("Error opening image file: %s", err)
}
base64 := base64.StdEncoding.EncodeToString(imgFile)
return base64, nil
}
Now, I'm writing table driven tests for this function and they right now look like this.
func TestGetFile(t *testing.T) {
type getFileTest struct {
Path string
Base64 string
Err error
}
getFileTests := []getFileTest{
{"", "", nil},
}
for _, td := range getFileTests {
base64, err := getFile(td.Path)
if err != nil {
t.Errorf("TestGetFile: Error calling getFile: %s", err)
}
if base64 != td.Base64 {
t.Errorf("TestGetFile: Return values from getFile is not expected: Expected: %s, Returned: %s", td.Base64, base64)
}
}
}
Now, current test fails with:-
test.go:18: TestGetFile: Error calling getFile: Error opening image file: open : no such file or directory
How do I assert that I get the right error when I pass empty path to getFile
?
The os
package provides the os.IsNotExist
function to check the various file existence errors:
if err != nil && os.IsNotExist(err) {
fmt.Println("file doesn't exist")
}
On a more general level, you can use a strings.Contains()
call on err.Error()
(which returns the string of the error message). Example:
if err == nil || !strings.Contains(err.Error(), "no such file or directory") {
// we didn't get the error we were expecting
}
Note, however, that string matching is rather fragile. If the package author changes the error message, your test might start failing despite the correct error still being returned.
This is why the idiom in Go is usually for packages to define the error types they return as package-level constants, or more often, variables, since fmt.Errorf()
and errors.New()
return error
interface types, which by definition can't be constant, since interfaces are reference types. As package-level constants, they can be directly tested for. Example, the sql
package defines sql.ErrNoRows
, so you can quickly and easily test if the error a query returned indicates that there were no result rows.
You can either create a "constant" and use it as a reference for comparison like that :
var ErrFile = errors.New("Error opening image file")
func getFile(path string) (string, error) {
return "", ErrFile
}
func main() {
f := "problem.txt"
_, err := getFile(f)
if err == ErrFile {
fmt.Printf("Problem with file %s", f)
}
}
Or, if you need more flexibility, you can create your own error type.
type FileError struct {
Path string
}
func (e *FileError) Error() string {
return fmt.Sprintf("Error opening image file: %s", e.Path)
}
func getFile(path string) (string, error) {
return "", &FileError{Path: path}
}
func main() {
f := "problem.txt"
_, err := getFile(f)
if ferr, ok := err.(* FileError); ok {
fmt.Println(ferr)
}
}