httptesting source

#testify/assert

package hello_httptesting

import (
	"net/http"
	"testing"

	"github.com/stretchr/testify/assert"
)

// using assert to test simple handlerfunc

func TestAssertHTTPBodyContains_HelloWorld(t *testing.T) {
	// mock server for testing clients etc.
	handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte("HelloWorld"))
	})

	assert.HTTPBodyContains(t, handler, "GET", "/", nil, "HelloWorld")
}

#gavv/httpexpect

import "github.com/gavv/httpexpect"

func TestMiddlewareWithHTTPEXpect(t *testing.T) {
	handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte("HelloWorld"))
	})

	// run server using httptest
	server := httptest.NewServer(StopWatch(handler))
	defer server.Close()

	// create httpexpect instance
	e := httpexpect.New(t, server.URL)
	e.GET("/").Expect().Status(http.StatusOK).Body().Equal("HelloWorld")
}

#httptest.NewRecorder

import "net/http/httptest"

func TestMiddlewareWithRecorder(t *testing.T) {
	handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte("HelloWorld"))
	})

	req, _ := http.NewRequest("GET", "/", nil)
	rec := httptest.NewRecorder()

	// running request
	StopWatch(handler).ServeHTTP(rec, req)

	assert.Equal(t, rec.Body.String(), "HelloWorld")
}

#HandlerTestSuite

//  - httptesting/middleware_stopwatch.go - 
package httptesting

import (
	"log"
	"net/http"
	"time"
)

type stopwatch struct {
	req   *http.Request
	start time.Time
}

func start(req *http.Request) *stopwatch {
	return &stopwatch{
		req:   req,
		start: time.Now(),
	}
}

func (s *stopwatch) stop() {
	log.Printf("[%s] %q %v\n", s.req.Method, s.req.URL.String(), time.Now().Sub(s.start))
}

func StopWatch(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer start(r).stop()

		next.ServeHTTP(w, r)
	})
}
//  - httptesting/handler_testing_suite.go - 
package httptesting

import (
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/gavv/httpexpect"
	"github.com/stretchr/testify/suite"
)

type HandlerTestSuite struct {
	suite.Suite

	server *httptest.Server

	Handler http.Handler
}

// Interface implementation
func (suite *HandlerTestSuite) SetupSuite() {
	suite.server = httptest.NewServer(suite.Handler)
}

func (suite *HandlerTestSuite) TearDownSuite() {
	suite.server.Close()
}

func (suite *HandlerTestSuite) TestPublic() {
	expect := Expect(suite.server.URL, suite.T())

	e := expect.GET("/public").Expect()
	e.Status(http.StatusOK).Body().Equal("this is public endpoint")
}

func (suite *HandlerTestSuite) TestAuthorizeMethodPost() {
	expect := Expect(suite.server.URL, suite.T())
	e := expect.GET("/authorize").Expect()
	o := e.Status(http.StatusMethodNotAllowed).JSON()
	o.Object().ValueEqual("message", "Method Not Allowed")
}

func (suite *HandlerTestSuite) TestAuthorizeLogin() {
	// getting token
	type Login struct {
		Username string `form:"user"`
		Password string `form:"pass"`
	}

	tests := []struct {
		name       string
		user, pass string
		code       int
	}{
		{
			name: "bad password",
			user: "foo",
			pass: "baz",
			code: http.StatusUnauthorized,
		},
		{
			name: "bad user",
			user: "fuz",
			pass: "bar",
			code: http.StatusUnauthorized,
		},
		{
			name: "ok user&password",
			user: "foo",
			pass: "bar",
			code: http.StatusOK,
		},
	}

	for _, test := range tests {
		test := test
		suite.T().Run(test.name, func(tt *testing.T) {
			expect := Expect(suite.server.URL, tt)

			e := expect.POST("/authorize").WithForm(Login{test.user, test.pass}).Expect()
			e.Status(test.code)
		})
	}
}

func (suite *HandlerTestSuite) TestRestrictedStatus() {
	expect := Expect(suite.server.URL, suite.T())

	// getting token
	type Login struct {
		Username string `form:"user"`
		Password string `form:"pass"`
	}
	r := expect.POST("/authorize").WithForm(Login{"foo", "bar"}).
		Expect().
		Status(http.StatusOK).JSON().Object()

	tests := []struct {
		name       string
		hKey, hVal string
		code       int
	}{
		{
			name: "Bad Header Token",
			hKey: "foo",
			hVal: "baz",
			code: http.StatusBadRequest,
		},
		{
			name: "Bad Token",
			hKey: "Authorization",
			hVal: "Bearer <bad token>",
			code: http.StatusUnauthorized,
		},
		{
			name: "Good token",
			hKey: "Authorization",
			hVal: "Bearer " + r.Value("token").String().Raw(),
			code: http.StatusOK,
		},
	}

	for _, test := range tests {
		test := test
		suite.T().Run(test.name, func(tt *testing.T) {
			expect := Expect(suite.server.URL, tt)

			e := expect.POST("/restricted").WithHeader(test.hKey, test.hVal).Expect()
			e.Status(test.code)
		})
	}
}

func (suite *HandlerTestSuite) TestRestrictedContent() {
	expect := Expect(suite.server.URL, suite.T())

	// getting token
	type Login struct {
		Username string `form:"user"`
		Password string `form:"pass"`
	}

	r := expect.POST("/authorize").WithForm(Login{"foo", "bar"}).
		Expect().
		Status(http.StatusOK).JSON().Object()

	e := expect.POST("/restricted").
		WithHeader("Authorization", "Bearer "+r.Value("token").String().Raw()).
		Expect()
	e.Status(http.StatusOK).Body().Equal("Welcome, foo\n")
}

// wrapper for httpexpect, so it fail with correct test id.
func Expect(url string, t *testing.T) *httpexpect.Expect {
	return httpexpect.WithConfig(httpexpect.Config{
		BaseURL:  url,
		Reporter: httpexpect.NewAssertReporter(t),
		Printers: nil,
	})
}
//  - httptesting/middleware_panic.go - 
package httptesting

import (
	"log"
	"net/http"
)

func NoPanics(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// panic recovery
		defer func() {
			if err := recover(); err != nil {
				log.Printf("panic: %+v", err)
				http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
			}
		}()

		next.ServeHTTP(w, r)
	})
}