github.com/rschmied/mockresponder

A simple HTTP responder


License
MIT
Install
go get github.com/rschmied/mockresponder

Documentation

Go ReferenceCodeQL Go Coverage StatusGo Report Card

mockresponder

A simple HTTP responder

This package allows to provide mock (API) responses to client code. The responder has a canned set of responses which can be matched against URL matched via regex. Each response can include a status code (default is 200), returned data as []byte, and a potential error. Each response is only served once, if the provided data runs out of responses, it will panic.

For this to work, our API client needs to use an interface which satisfies the http.Client as well as our mock responder.

Something like this goes into the API client code:

type apiClient interface {
    Do(req *http.Request) (*http.Response, error)
}

type Client struct {
    host             string
    apiToken         string
    httpClient       apiClient
}

Normal operation would then store a http.Client instance into the httpClient. When debugging, though, this can be initialized with an instance of mockResponder and this will then allow to provide mocked responses.

Here's an example of a unit test:

func TestClient_token_auth(t *testing.T) {

    // returns a Client as defined above, having an apiClient attribute
    c := NewClient("https://bla.bla")
    mrClient, ctx := mr.NewMockResponder()
    c.httpClient = mrClient

    tests := []struct {
        name      string
        responses mr.MockRespList
        wantErr   bool
        errstr    string
    }{
        {
            "goodtoken",
            mr.MockRespList{
                mr.MockResp{
                    Data: []byte(`"OK"`),
                },
                mr.MockResp{
                    Data: []byte(`{"version": "2.4.1","ready": true}`),
                },
            },
            false,
            "",
        },
        {
            "badjson",
            mr.MockRespList{
                mr.MockResp{
                    Data: []byte(`,,,`),
                },
            },
            true,
            "invalid character ',' looking for beginning of value",
        },
        {
            "badtoken",
            mr.MockRespList{
                mr.MockResp{
                    Data: []byte(`{
                        "description": "No authorization token provided.",
                        "code": 401
                    }`),
                    Code: 401,
                },
            },
            true,
            "invalid token but no credentials provided",
        },
        {
            "clienterror",
            mr.MockRespList{
                mr.MockResp{
                    Data: []byte{},
                    Err:  errors.New("ka-boom"),
                },
            },
            true,
            "ka-boom",
        },
    }
    for _, tt := range tests {
        mrClient.SetData(tt.responses)
        var err error
        t.Run(tt.name, func(t *testing.T) {
            if err = c.versionCheck(ctx); (err != nil) != tt.wantErr {
                t.Errorf("Client.versionCheck() error = %v, wantErr %v", err, tt.wantErr)
            }
        })
        if !mrClient.Empty() {
            t.Error("not all data in mock client consumed")
        }
        if tt.wantErr {
            assert.EqualError(t, err, tt.errstr)
        }
    }
}

(c) 2022 Ralph Schmieder