Reputation: 797
Is there a best practice for comparing non-opaque error values in Go?
Most code bases seem to treat errors as opaque (either the operation succeeded or it failed, with no visibility into internal details about what caused the error).
This makes it easy to write unit tests since all you need to do is assert the actual error against the expected error. The most I've seen people do in addition to that is compare error strings to make sure that it includes at minimum some key information. For example:
if err == nil || !strings.Contains(err.Error(), "not found in the Raft configuration") {
t.Fatalf("err: %v", err)
}
But for cases where the additional error information is needed (like in form validation where you need to specify the invalid field name, the value, an error code and possibly some nested errors too), what do you do?
I can't simply do a reflect.DeepEqual()
since the error will contain a unique stack trace which will make the comparison fail every time. Also, having nested errors makes the testing logic even more complex and you'll need all kinds of error-prone logic (loops, recursion) to compare the desired fields. Is there a standard way of testing errors of this nature?
Upvotes: 2
Views: 177
Reputation: 6274
While this may not answer your question pertaining to checking errors that are not done the right way, here is at least an example of how to create and use errors the right way.
What is commonly done in the standard library packages is errors are created as exported variables and then those can be thrown and compared. Here is an example... however unimaginative it may be.
https://play.golang.org/p/JlpguFn2NX
package main
import (
"errors"
"fmt"
)
var ErrBadIdea = errors.New("that was dumb")
var ErrNoValue = errors.New("not a real value")
func DoSomething(i int) error {
if i == 0 {
return ErrNoValue
}
if i < 0 {
return ErrBadIdea
}
// does something that doesn't like negative or zero values
return nil
}
func main() {
err := DoSomething(-1)
fmt.Println(err == ErrBadIdea, err)
err = DoSomething(0)
fmt.Println(err == ErrNoValue, err)
}
An excellent example of this is in the database/sql
package of the standard library.
https://godoc.org/database/sql#pkg-variables
These variables exist so you can test like so
if err == sql.ErrNoRows { // these are not the rows you are looking for }
It gets better!
Because error
is an interface
, all you have to do is implement it. Which only requires you have a func Error() string
. See the following example and marvel at the power of interface
s.
https://play.golang.org/p/3RFrxKGkdm
Also read the following blog about errors.
https://blog.golang.org/error-handling-and-go
Upvotes: 1