graniticio / granitic

Web/micro-services and IoC framework for Golang developers
https://granitic.io/
Apache License 2.0
35 stars 12 forks source link

Providing WsResponse Errors as an interface? #27

Open milap-neupane opened 5 years ago

milap-neupane commented 5 years ago

The ws.Response struct currently provides Errors as a *ServiceErrors struct which we can use to add predefined errors. It is difficult to write tests for this. If the Errors is provided as an interface it would be easy to write tests.

Current implementation:

type Response struct {
   Errors *ServiceErrors
...
}

Expected something like:


type Response struct {
   Errors ManagedServiceErrors
...
}

type ManagedServiceErrors interface {
  AddError(*CategorisedError)
  AddNewError(ServiceErrorCategory, string, string)
  AddPredefinedError(string, ...string) error
  HasErrors() bool
}
benhalstead commented 5 years ago

This is definitely worth looking at @milap-neupane

Could you give a little bit of detail about why the current structure is hard to test?

milap-neupane commented 5 years ago

The response is as follow:

type Response struct {
   Errors *ServiceErrors
...
}

The ServiceErrors struct is defined in granitic as:

type ServiceErrors struct {
    // All services found, in the order in which they occured.
    Errors []CategorisedError

    // An externally computed HTTP status code that reflects the mix of errors in this structure.
    HTTPStatus int

    // A component able to find additional information about error from that error's unique code.
    ErrorFinder ServiceErrorFinder
}

And we are using the method AddPredefinedError to add predefined error in the endpoint. The endpoint looks like:

func (rl *CreateLogic) ProcessPayload(ctx context.Context, req *ws.WsRequest, res *ws.WsResponse, rcr *CreateRequest) {
    err := rl.CreateFuctionality(rcr)
    if err != nil {
        res.Errors.AddPredefinedError("DUPLICATE_CREATE") // The functionaliy we want to test
    }
}

To test the above component we generate err we create a mock implementation of CreateLogic with the method CreateFuctionality returning an error. Now we want to test that in case of error res.Errors.AddPredefinedError has been called or set.

Now since the res.Errors is a struct defined in granitic the method AddPredefinedError cannot be implemented in a different way for test because of which the test will panic

panic: runtime error: invalid memory address or nil pointer dereference [recovered]
    panic: runtime error: invalid memory address or nil pointer dereference
....
    /Users/me/.gvm/gos/go1.11.1/src/runtime/panic.go:513 +0x1b9
github.com/graniticio/granitic/ws.(*ServiceErrors).AddPredefinedError(0x0, 0x132dc99, 0x12, 0x0, 0x0, 0x0, 0x132adf7, 0x9)
    /Users/me/.gvm/pkgsets/go1.11.1/global/pkg/mod/github.com/graniticio/granitic@v1.3.0-pre1/ws/error.go:102 +0x5a
....
benhalstead commented 5 years ago

So the design mistake (in Granitic) is that Response.Errors probably shouldn't have been defined as a pointer.

In the short term as long as your tests initialise Response.Errors your tests should work? E.g.

res := ws.NewResponse()

https://godoc.org/github.com/graniticio/granitic/ws#NewResponse