How to nest endpoints in a API client with interfaces while keeping readability

257 views Asked by At

I'm trying to compose a simple API client I'm stuck trying to figure out how to make it readable and testable. How can I compose a nested structure while keeping it testable?

Psuedo code:

type VehicleEndpoint struct {
    Car CarEndpoint
    VehicleGetter
}

type VehicleGetter interface {
    Get(string) Vehicle
}

type Vehicle struct {
    kind string
}

type VehicleClient struct {
    http.Client
    url string
}

func (v *VehicleClient) Get(kind string) Vehicle {
    resp := v.Do(v.url, kind)
    return Vehicle{
        kind: resp.Kind
    }
}


type CarEndpoint struct
...

type CarGetter interface
...

type Car struct
...

type CarClient struct
...


type API struct {
    Vehicle VehicleEndpoint
}

api := API{
    Vehicle: VehicleEndpoint{
        VehicleGetter: VehicleClient{
            http.Client{},
        }
        Car: CarEndpoint{
          CarGetter: CarClient{
            http.Client{},
          }
       }
    }
}

Now I can call API like so:

api.Vehicle.Car.Get(kind)

This gives me a very readable (nested) implementation to work with however I'm having a hard time mocking these endpoints because the use of interface would effectively remove any recognition of the nested structure. What would be the recommended way to construct an API keeping it very readable while also having each endpoint mocked?

1

There are 1 answers

0
Dmitry Harnitski On BEST ANSWER

You are fighting with language and bringing your OOP hobbies into language that is not designed for that.

I personally would change direction and use old good flat structures and functions.

Although, if you want to continue with your design, you can mock not interface but whole http stack. You can test your code with much better confidence as you testing real http payload vs making calls to your interfaces.

Inject HttpClient into Vehicle: func NewVehicle(httpClient *http.Client){}

In test code, use *http.ServeMux:

mux.Handle("/path1", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // assessments and mocked response
}))
mux.Handle("/path2", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // assessments and mocked response
}))
// fallback to show not implemented routes
result.mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        result.t.Errorf("Not Supported route %q", r.URL.Path)
}))

Build Http Server:

server := httptest.NewServer(mux)

Create Http Client from mux server:

client := server.Client()