Compare 2 errors in Go for unit test

Viewed 1697

I got a problem like below: Compare 2 errors when writing unit test

package main

import (
    "errors"
    "fmt"
    "reflect"
    
    "github.com/google/go-cmp/cmp"
    "github.com/google/go-cmp/cmp/cmpopts"
)

func main() {
    er1 := errors.New("database name is not exists")
    er2 := errors.New("database name is not exists")
    
    result1 := reflect.DeepEqual(er1, er2)
    fmt.Println("reflect: ", result1)
    
    result2 := cmp.Equal(er1, er2, cmpopts.EquateErrors())
    fmt.Println("compare: ", result2)
    
    result3 := errors.Is(er1, er2)
    fmt.Println("errorIs: ", result3)
}

And the output of the above code is:

reflect:  true
compare:  false
errorIs:  false

I want to compare 2 error and reflect.DeepEqual(er1, er2) is the 1st approach I apply and this method produce the output I want, but this approach have a warning from go lint:

avoid using reflect.DeepEqual with errorsdeepequalerrors

After google search, some people tell me some approaches that are:

  • Use cmp package to compare: cmp.Equal(er1, er2, cmpopts.EquateErrors())
  • Use errors package to compare: errors.Is(er1, er2)

But both above approaches can not produce same result like the 1st approach (use reflect.DeepEqual). How I can compare 2 errors without warning from go lint and produce the result like the reflect.DeepEqual Tks

2 Answers

Depending on how you write your tests, you may depend on reflect.DeepEqual() and ignore the linter warning ;
the drawback is : you start depending on the inner structure of the errors you return.


In the testing code I read, and the testing code we write, we use one of the following patterns :

  • most of the time, we just compare the error to nil ;
  • in some cases our functions return predefined error values, and we test for these specific values :
package pkg

var ErrUnboltedGizmo = errors.New("gizmo is unbolted")

// in test functions, depending on the case :
if err == pkg.ErrUnboltedGizmo { ...
// or :
if errors.Is(err, pkg.ErrUnboltedGizmo) { ...
  • when our production code mandates that a specific error be spotted (a common use case is io.EOF), we write code that dutifully wraps that known error, and we use errors.Is() (both in production code and in testing code),
  • when the need is, in testing only, to loosely confirm that an error matches something and not something else (e.g : Parse error and not File not found), we simply search for strings in the error message :
if err == nil || !strings.Contains(err.Error(), "database name is not exists") {
    t.Errorf("unexpected error : %s", err)
}

What I found useful is to use cmp.Diff with cmpopts.IgnoreFields to ignore the sepecific error fields which cause the issue u detailed and afterward I just run a check on the errors with string.Contains or whatever I find fitting.

So it goes something like this:

if diff := cmp.Diff(expected, got, cmpopts.IgnoreFields(expected, "ErrorField")); diff != "" { 
          // found diff not including the error
}

Now check only the errors on their own and that's it.

And yeah I know u marked a solution already but maybe it will help someone :)

Related