httpspec

package
v0.142.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 16, 2023 License: Apache-2.0 Imports: 14 Imported by: 0

README

httpspec

httpspec allow you to create HTTP API specifications with ease.

Documentation

The documentation maintained in GoDoc, including the examples.

Usage

package mypkg

func TestMyHandlerCreate(t *testing.T) {
	s := testcase.NewSpec(t)

	// subject
	httpspec.SubjectLet(s, func(t *testcase.T) http.Handler {
		return MyHandler{}
	})

	// Arrange
	httpspec.ContentTypeIsJSON(s)
	httpspec.Method.LetValue(s, http.MethodPost)
	httpspec.Path.LetValue(s, `/`)
	httpspec.Body.Let(s, func(t *testcase.T) interface{} {
		// this will end up as {"foo":"bar"} in the request body
		return map[string]string{"foo": "bar"}
	})

	s.Then(`it will...`, func(t *testcase.T) {
		// Act
		rr := httpspec.SubjectGet(t)

		// Assert
		assert.Must(t).Equal( http.StatusOK, rr.Code)
		var resp CreateResponse
		assert.Must(t).Nil( json.Unmarshal(rr.Body.Bytes(), &resp))
		// ...
	})
}

Documentation

Overview

Example (Usage)
var tb testing.TB
s := testcase.NewSpec(tb)

// subject
httpspec.Handler.Let(s, func(t *testcase.T) http.Handler {
	return MyHandler{}
})

// Arrange
httpspec.ContentTypeIsJSON(s)
httpspec.Method.LetValue(s, http.MethodPost)
httpspec.Path.LetValue(s, `/`)
httpspec.Body.Let(s, func(t *testcase.T) interface{} {
	// this will end up as {"foo":"bar"} in the request body
	return map[string]string{"foo": "bar"}
})

s.Then(`it will...`, func(t *testcase.T) {
	// ServeHTTP
	rr := httpspec.ServeHTTP(t)

	// Assert
	t.Must.Equal(http.StatusOK, rr.Code)
	var resp CreateResponse
	t.Must.Nil(json.Unmarshal(rr.Body.Bytes(), &resp))
	// ...
})
Output:

Example (UsageWithDotImport)
s := testcase.NewSpec(testingT)

Handler.Let(s, func(t *testcase.T) http.Handler { return MyHandler{} })

s.Before(func(t *testcase.T) {
	t.Log(`given authentication header is set`)
	Header.Get(t).Set(`X-Auth-Token`, `token`)
})

s.Describe(`GET / - list of X`, func(s *testcase.Spec) {
	Method.LetValue(s, http.MethodGet)
	Path.LetValue(s, `/`)

	var onSuccess = func(t *testcase.T) ListResponse {
		rr := ServeHTTP(t)
		t.Must.Equal(http.StatusOK, rr.Code)
		// unmarshal the response from rr.body
		return ListResponse{}
	}

	s.And(`something is set in the query`, func(s *testcase.Spec) {
		s.Before(func(t *testcase.T) {
			Query.Get(t).Set(`something`, `value`)
		})

		s.Then(`it will react to it as`, func(t *testcase.T) {
			listResponse := onSuccess(t)
			// assert
			_ = listResponse
		})
	})

	s.Then(`it will return the list of resource`, func(t *testcase.T) {
		listResponse := onSuccess(t)
		// assert
		_ = listResponse
	})
})

s.Describe(`GET /{resourceID} - show X`, func(s *testcase.Spec) {
	Method.LetValue(s, http.MethodGet)
	Path.Let(s, func(t *testcase.T) string {
		return fmt.Sprintf(`/%s`, t.Random.String())
	})

	var onSuccess = func(t *testcase.T) ShowResponse {
		rr := ServeHTTP(t)
		t.Must.Equal(http.StatusOK, rr.Code)
		// unmarshal the response from rr.body
		return ShowResponse{}
	}

	s.Then(`it will return the resource 'show'' representation`, func(t *testcase.T) {
		showResponse := onSuccess(t)
		// assert
		_ = showResponse
	})
})
Output:

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// Handler prepares the current testcase spec scope to be ready for http handler testing.
	// You define your spec subject with this and all the request will be pointed towards this.
	Handler = testcase.Var[http.Handler]{ID: `httpspec:Handler`}
	// Context allow to retrieve the current test scope's request context.
	Context = testcase.Var[context.Context]{ID: `httpspec:Context`, Init: func(t *testcase.T) context.Context {
		return context.Background()
	}}
	Method = testcase.Var[string]{ID: `httpspec:Method`, Init: func(t *testcase.T) string {
		return http.MethodGet
	}}
	Path = testcase.Var[string]{ID: `httpspec:Path`, Init: func(t *testcase.T) string {
		return `/`
	}}
	// Query allows you to retrieve the current test scope's http PathGet query that will be used for ServeHTTP.
	// In a Before Block you can access the query and then specify the values in it.
	Query = testcase.Var[url.Values]{ID: `httpspec:Query`, Init: func(t *testcase.T) url.Values {
		return url.Values{}
	}}
	// Header allows you to set the current test scope's http PathGet for ServeHTTP.
	Header = testcase.Var[http.Header]{ID: `httpspec:Header.Get`, Init: func(t *testcase.T) http.Header {
		return http.Header{}
	}}
)
View Source
var (
	InboundRequest = testcase.Var[*http.Request]{
		ID: "httpspec:InboundRequest",
		Init: func(t *testcase.T) *http.Request {
			target, _ := url.Parse(Path.Get(t))
			target.RawQuery = Query.Get(t).Encode()
			if isDebugEnabled(t) {
				t.Log(`http.InboundRequest.Method:`, Method.Get(t))
				t.Log(`http.InboundRequest.Path`, target.String())
			}
			r := httptest.NewRequest(Method.Get(t), target.String(), asIOReader(t, Header.Get(t), Body.Get(t)))
			r = r.WithContext(Context.Get(t))
			r.Header = Header.Get(t)
			return r
		},
	}
	OutboundRequest = testcase.Var[*http.Request]{
		ID: "httpspec:OutboundRequest",
		Init: func(t *testcase.T) *http.Request {
			u := url.URL{
				Scheme:   t.Random.SliceElement([]string{"http", "https"}).(string),
				Host:     fmt.Sprintf("www.%s.com", t.Random.StringNC(7, random.CharsetAlpha())),
				Path:     Path.Get(t),
				RawPath:  Path.Get(t),
				RawQuery: Query.Get(t).Encode(),
			}
			if isDebugEnabled(t) {
				t.Log(`http.OutboundRequest.Method:`, Method.Get(t))
				t.Log(`http.OutboundRequest.Path`, u.Path)
			}
			r, err := http.NewRequest(Method.Get(t), u.String(), asIOReader(t, Header.Get(t), Body.Get(t)))
			t.Must.Nil(err)
			r = r.WithContext(Context.Get(t))
			r.Header = Header.Get(t)
			return r
		},
	}
	ResponseRecorder = testcase.Var[*httptest.ResponseRecorder]{
		ID: "httpspec:ResponseRecorder",
		Init: func(t *testcase.T) *httptest.ResponseRecorder {
			return httptest.NewRecorder()
		},
	}
	Response = testcase.Var[*http.Response]{
		ID: "httpspec:Response",
		Init: func(t *testcase.T) *http.Response {
			code := t.Random.SliceElement([]int{
				http.StatusOK,
				http.StatusTeapot,
				http.StatusInternalServerError,
			}).(int)
			body := t.Random.String()
			return &http.Response{
				Status:     http.StatusText(code),
				StatusCode: code,
				Proto:      "HTTP/1.0",
				ProtoMajor: 1,
				ProtoMinor: 0,
				Header: http.Header{
					"X-" + t.Random.StringNWithCharset(5, "ABCD"): {t.Random.StringNWithCharset(5, "ABCD")},
				},
				Body:          io.NopCloser(strings.NewReader(body)),
				ContentLength: int64(len(body)),
			}
		},
	}
)
View Source
var Body = testcase.Var[any]{ID: `httpspec:Body`, Init: func(t *testcase.T) any {
	return &bytes.Buffer{}
}}
View Source
var Request = InboundRequest

Request

DEPRECATED: use InboundRequest instead

Functions

func ClientDo added in v0.98.0

func ClientDo(t *testcase.T, srv *httptest.Server, r *http.Request) (*http.Response, error)

func ContentTypeIsJSON added in v0.13.0

func ContentTypeIsJSON(s *testcase.Spec)
Example
s := testcase.NewSpec(testingT)

Handler.Let(s, func(t *testcase.T) http.Handler { return MyHandler{} })
ContentTypeIsJSON(s)

s.Describe(`POST / - create X`, func(s *testcase.Spec) {
	Method.LetValue(s, http.MethodPost)
	Path.LetValue(s, `/`)

	Body.Let(s, func(t *testcase.T) interface{} {
		// this will end up as {"foo":"bar"} in the request body
		return map[string]string{"foo": "bar"}
	})

	var onSuccess = func(t *testcase.T) CreateResponse {
		rr := ServeHTTP(t)
		assert.Must(t).Equal(http.StatusOK, rr.Code)
		var resp CreateResponse
		assert.Must(t).Nil(json.Unmarshal(rr.Body.Bytes(), &resp))
		return resp
	}

	s.Then(`it will create a new resource`, func(t *testcase.T) {
		createResponse := onSuccess(t)
		// assert
		_ = createResponse
	})
})
Output:

func Debug added in v0.5.1

func Debug(s *testcase.Spec)

func ItBehavesLikeHandlerMiddleware added in v0.84.0

func ItBehavesLikeHandlerMiddleware(s *testcase.Spec, subject MakeHandlerMiddlewareFunc)

func ItBehavesLikeRoundTripperMiddleware added in v0.76.2

func ItBehavesLikeRoundTripperMiddleware(s *testcase.Spec, subject MakeRoundTripperFunc)

func LetRequest added in v0.98.0

func LetRequest(s *testcase.Spec, rv RequestVar) testcase.Var[*http.Request]

func LetResponseRecorder added in v0.95.0

func LetResponseRecorder(s *testcase.Spec) testcase.Var[*httptest.ResponseRecorder]

func LetRoundTripperDouble added in v0.111.0

func LetRoundTripperDouble(s *testcase.Spec) testcase.Var[*RoundTripperDouble]

func LetServer added in v0.96.0

func LetServer(s *testcase.Spec, handler testcase.VarInit[http.Handler]) testcase.Var[*httptest.Server]

func ServeHTTP

func ServeHTTP(t *testcase.T) *httptest.ResponseRecorder

ServeHTTP will make a request to the spec context it requires the following spec variables

  • Method -> http MethodGet <string>
  • Path -> http PathGet <string>
  • Query -> http query string <url.Values>
  • Body -> http payload <io.Reader|io.ReadCloser>

Types

type HandlerMiddlewareContract added in v0.84.0

type HandlerMiddlewareContract struct {
	Subject MakeHandlerMiddlewareFunc
	MakeCTX testcase.VarInit[context.Context]
}

func (HandlerMiddlewareContract) Spec added in v0.84.0

type MakeHandlerMiddlewareFunc added in v0.84.0

type MakeHandlerMiddlewareFunc func(t *testcase.T, next http.Handler) http.Handler

type MakeRoundTripperFunc added in v0.84.0

type MakeRoundTripperFunc func(t *testcase.T, next http.RoundTripper) http.RoundTripper

type RequestVar added in v0.98.0

type RequestVar struct {
	Context testcase.Var[context.Context]
	Scheme  testcase.Var[string]
	Host    testcase.Var[string]
	Method  testcase.Var[string]
	Path    testcase.Var[string]
	Query   testcase.Var[url.Values]
	Header  testcase.Var[http.Header]
	Body    testcase.Var[any]
}

type RoundTripperDouble added in v0.110.0

type RoundTripperDouble struct {
	// RoundTripperFunc is an optional argument in case you want to stub the response
	RoundTripperFunc RoundTripperFunc
	// ReceivedRequests hold all the received http request.
	ReceivedRequests []*http.Request
}

func (*RoundTripperDouble) LastReceivedRequest added in v0.110.0

func (d *RoundTripperDouble) LastReceivedRequest(tb testing.TB) *http.Request

func (*RoundTripperDouble) RoundTrip added in v0.110.0

func (d *RoundTripperDouble) RoundTrip(r *http.Request) (*http.Response, error)

type RoundTripperFunc added in v0.110.0

type RoundTripperFunc func(r *http.Request) (*http.Response, error)

func (RoundTripperFunc) RoundTrip added in v0.110.0

func (fn RoundTripperFunc) RoundTrip(request *http.Request) (*http.Response, error)

type RoundTripperMiddlewareContract added in v0.76.2

type RoundTripperMiddlewareContract struct {
	Subject MakeRoundTripperFunc
	MakeCTX testcase.VarInit[context.Context]
}

func (RoundTripperMiddlewareContract) Spec added in v0.76.2

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL