Use dependency injection and mock the objects to simulate the errors.

In my opinion though, there is no need for an error to hold references to 
its underlying errors. Errors are just values. You take their values, add 
any additional information, and create a new error. You shouldn't need to 
test whether the error contains any specific underlying error. The point of 
'wrapping' error is to prevent access to the underlying errors. I will show 
some examples:

Given you have the following layers: presentation->domain->data. Let's say 
a user attempts to sign in with an invalid username. The data layer checks 
with the database and finds out that there is no such user. So the data 
layer component returns data.ErrMissingUser. Upon receiving the error, the 
domain layer takes the data.ErrMissingUser's error message, adds any 
additional information, and returns a new error domain.ErrInvalidUser to 
the presentation layer. For testing the domain layer component, mock the 
data component and simulate it to return the data.ErrMissingUser. Then in 
your test, check whether the domain component returns domain.ErrInvalidUser.

When dealing with non-predefined errors, let's say there is a database 
error, the data layer returns the error to the domain layer. The domain 
layer takes the error message, adds any additional information, and creates 
a new error. When testing the domain layer, mock the data component and 
have it return an error. Then, in your test, ensure that the domain 
component returns an error. 

Different people may have different approach to this.


On Wednesday, April 5, 2017 at 7:29:24 PM UTC+7, jlb1...@gmail.com wrote:

> Let's say I have an app with three layers: a view (JSON endpoint), 
> service, and persistence layer.
>
>
> Now a `NotFoundError` error occurs at the persistence layer when a record 
> cannot be found. `NotFoundError` is a simple wrapper around a lower level 
> database driver error that I don't want the application to be aware of 
> (since it's an implementation detail).
>
>
> For example, if the `gocql` driver emits an `ErrNotFound` error, the 
> persistence layer will wrap it in a custom error type so that the original 
> error is preserved but not exposed.
>
>
> package database
>
> type NotFoundError struct {
>   originalErr error // persistence layer wraps the driver error
> }
>
>
>
> The service layer then type switches on whatever errors it receives from 
> lower layers. For example in a call to the persistence layer:
>
>
>
> package service
>
> func (s *Service) GetSomething(id string) (string, error) {
>   s, err := database.Get(id)
>   if err != nil {
>     return "", handleError(err, “service: failed to get something”)
>   }
> }
>
>
> func handleError(err error, context, message string) error {
>   switch err.(type) {
>     case database.NotFoundError:
>       return &ServiceError{
>         Code: NotFoundCode,
>         Message: message,
>         originalErr: errors.Wrap(err, context),
>       }
>     default:
>      ...
>   }
> }
>
>
>
> a service error is a custom error type that looks like:
>
>
> type ServiceError struct {
>  originalErr error
>  Code    int             `json:"code"`
>  Field string            `json:"target,omitempty"`
>  Message string          `json:"message,omitempty"`
>  Details []*ServiceError `json:"details,omitempty"`
> }
>
>
>
>
> To recap, the error propagates through the following layers:
>
>
> driver -> persistence -> service -> view
>
>
> Each layer type switches on the error type it receives from the preceding 
> layer. 
>
> Errors may be recursively nested (the "Details" field is a 
> []*ServiceError) and "originalErr" contains the original error with a stack 
> trace provided by the pkg/errors <https://github.com/pkg/errors> library.
>
>
> How would one approach unit testing something like this? 
>
>
> Doing a simple reflect.DeepEqual on the actual and expected error values 
> would be ideal, but the stack trace contained in the actual error means 
> that the expected error will always fail the equality comparison (since 
> DeepEqual also parses unexported fields). In addition, the order of errors 
> in the `Details` slice is also unreliable (for example when validating 
> fields, the order shouldn't matter) which further complicates trying to 
> compare things. I’d have to pollute my tests with loops, manual comparisons 
> and recursive logic which will itself be error-prone.
>
>
> I’ve looked through some popular projects on GitHub and I can’t find any 
> examples of code similar to this this which leads me to believe that all 
> these abstraction hierarchies and complex errors types are horribly 
> unidiomatic Go... I now know why people say Go is more of a systems 
> language. But anyway... is there a better way to deal with errors in an app 
> structured like this? How would you go about comparing these types of error 
> values? How do you handle rich errors types with lots of contextual 
> information in your own web applications?
>

-- 
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+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to