Yeah, we have TestRoute. It has one issue though: It doesn't have support for passing a custom context. One option is to extend the method with yet argument but since it already has 9 (!!!), this seems like a huge mess. Therefore, I decided to invent a new small library for writing API tests. It uses structs heavily which means that adding features to it doesn't mean changing 100 lines of code (like adding another arg to TestRoute does). I hope that we can start using this library more in our tests as it was designed to be very flexible and powerfule. Signed-off-by: Ondřej Budai <ondrej@budai.cz>
99 lines
2.3 KiB
Go
99 lines
2.3 KiB
Go
package test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// APICall is a small function object for testing HTTP APIs
|
|
type APICall struct {
|
|
// http.Handler to run the call against
|
|
Handler http.Handler
|
|
|
|
// HTTP method, e.g. http.MethodPatch
|
|
Method string
|
|
|
|
// Request Path
|
|
Path string
|
|
|
|
// Request body. If nil, an empty body is sent
|
|
RequestBody RequestBody
|
|
|
|
// Request header. If nil, default header is sent
|
|
Header http.Header
|
|
|
|
// Request context. If nil, default context is used
|
|
Context context.Context
|
|
|
|
// Status that's expected to be received. If set to 0, the status is not checked
|
|
ExpectedStatus int
|
|
|
|
// Validator for the response body. If set to nil, the body is not validated
|
|
ExpectedBody BodyValidator
|
|
}
|
|
|
|
// Do performs the request as defined in the APICall struct.
|
|
//
|
|
// If any errors occur when doing the request, or any of the validators fail, t.FailNow() is called
|
|
// Note that HTTP error status is not checked if ExpectedStatus == 0
|
|
//
|
|
// The result of the HTTP call is returned
|
|
func (a APICall) Do(t *testing.T) APICallResult {
|
|
t.Helper()
|
|
|
|
var bodyReader io.Reader
|
|
if a.RequestBody != nil {
|
|
bodyReader = bytes.NewReader(a.RequestBody.Body())
|
|
}
|
|
|
|
req := httptest.NewRequest(a.Method, a.Path, bodyReader)
|
|
|
|
if a.Context != nil {
|
|
req = req.WithContext(a.Context)
|
|
}
|
|
|
|
req.Header = a.Header
|
|
if req.Header == nil {
|
|
req.Header = http.Header{}
|
|
}
|
|
|
|
if a.RequestBody != nil && a.RequestBody.ContentType() != "" {
|
|
req.Header.Set("Content-Type", a.RequestBody.ContentType())
|
|
}
|
|
respRecorder := httptest.NewRecorder()
|
|
a.Handler.ServeHTTP(respRecorder, req)
|
|
resp := respRecorder.Result()
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
require.NoErrorf(t, err, "%s: could not read response body", a.Path)
|
|
|
|
if a.ExpectedStatus != 0 {
|
|
assert.Equalf(t, a.ExpectedStatus, resp.StatusCode, "%s: SendHTTP failed for path", a.Path)
|
|
}
|
|
if a.ExpectedBody != nil {
|
|
err = a.ExpectedBody.Validate(body)
|
|
require.NoError(t, err, "%s: cannot validate response body", a.Path)
|
|
}
|
|
|
|
return APICallResult{
|
|
Body: body,
|
|
StatusCode: resp.StatusCode,
|
|
}
|
|
}
|
|
|
|
// APICallResult holds a parsed response for an APICall
|
|
type APICallResult struct {
|
|
// Full body as read from the server
|
|
Body []byte
|
|
|
|
// Status code returned from the server
|
|
StatusCode int
|
|
}
|