Test a Handler or a HandlerFunc
// example/basic_mock_client.go
package myapp
import (
"io"
"net/http"
)
func NameHandler(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, `{"name": "hexi"}`)
}
// example/basic_mock_client_test.go
package myapp
import (
"testing"
"github.com/Hexilee/htest"
)
func TestNameHandlerFunc(t *testing.T) {
htest.NewClient(t).
ToFunc(NameHandler).
Get("").
Test().
StatusOK().
JSON().
String("name", "hexi")
}
You can also test handler (*http.ServeMux, *echo.Echo .etc.)
// example/basic_mock_client.go
package myapp
import (
"io"
"net/http"
)
var (
Mux *http.ServeMux
)
func init() {
Mux = http.NewServeMux()
Mux.HandleFunc("/name", NameHandler)
}
func NameHandler(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, `{"name": "hexi"}`)
}
// example/basic_mock_client_test.go
package myapp
import (
"testing"
"github.com/Hexilee/htest"
)
func TestNameHandler(t *testing.T) {
htest.NewClient(t).
To(Mux).
Get("/name").
Test().
StatusOK().
JSON().
String("name", "hexi")
}
// example/basic_mock_client.go
package myapp
import (
"io"
"github.com/labstack/echo"
)
var (
server *echo.Echo
)
func init() {
server = echo.New()
server.GET("/name", NameHandlerEcho)
}
func NameHandlerEcho(c echo.Context) error {
return c.String(http.StatusOK, `{"name": "hexi"}`)
}
// example/basic_mock_client_test.go
package myapp
import (
"testing"
"github.com/Hexilee/htest"
)
func TestNameHandlerEcho(t *testing.T) {
htest.NewClient(t).
To(server).
Get("/name").
Test().
StatusOK().
JSON().
String("name", "hexi")
}
Send a http request and test the response
// request_test.go
func TestRequest_Send(t *testing.T) {
NewClient(t).
Get("https://api.github.com/users/Hexilee").
Send().
StatusOK().
JSON().
String("login", "Hexilee")
}
Set mock server to be tested (Do not need it when you test real server)
Set a HandlerFunc as mock server
// example/basic_mock_client_test.go
package myapp
import (
"testing"
"github.com/Hexilee/htest"
)
func TestNameHandlerFunc(t *testing.T) {
htest.NewClient(t).
ToFunc(NameHandler).
Get("").
Test().
StatusOK().
JSON().
String("name", "hexi")
}
Set a Handler as mock server
// example/basic_mock_client_test.go
package myapp
import (
"testing"
"github.com/Hexilee/htest"
)
func TestNameHandler(t *testing.T) {
htest.NewClient(t).
To(Mux).
Get("/name").
Test().
StatusOK().
JSON().
String("name", "hexi")
}
Construct htest.Request using different http methods
For example
- Get
// client.go
func (c Client) Get(path string) *Request
More
- Head
- Trace
- Options
- Connect
- Delete
- Post
- Put
- Patch
Set headers and return *Request for chaining-call
- SetHeader
// server_test.go
Mux.Get("/request/header", HeaderHandler)
// request_test.go
func HeaderHandler(w http.ResponseWriter, req *http.Request) {
if req.Header.Get(HeaderContentType) == MIMEApplicationJSON {
io.WriteString(w, `{"result": "JSON"}`)
return
}
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
}
func TestRequest_SetHeader(t *testing.T) {
client := NewClient(t).To(Mux)
// bad content type
client.
Get("/request/header").
SetHeader(HeaderContentType, MIMEApplicationForm).
Test().
StatusBadRequest()
// right
client.
Get("/request/header").
SetHeader(HeaderContentType, MIMEApplicationJSON).
Test().
StatusOK().
JSON().
String("result", "JSON")
}
HeaderContentType, MIMEApplicationForm are constants in const.go For more information, you can refer to Appendix
- SetHeaders
// request_test.go
func TestRequest_SetHeaders(t *testing.T) {
client := NewClient(t).To(Mux)
// bad content type
client.Get("/request/header").
SetHeaders(
map[string]string{
HeaderContentType: MIMEApplicationForm,
},
).
Test().
StatusBadRequest()
// right
client.Get("/request/header").
SetHeaders(
map[string]string{
HeaderContentType: MIMEApplicationJSON,
},
).
Test().
StatusOK().
JSON().
String("result", "JSON")
}
Add cookie and return *Request for chaining-call
// server_test.go
Mux.Get("/request/cookie", CookieHandler)
// request_test.go
var (
testCookie = http.Cookie{Name: "test_cookie", Value: "cookie_value"}
)
func CookieHandler(w http.ResponseWriter, req *http.Request) {
cookie, err := req.Cookie(testCookie.Name)
if err != nil {
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
return
}
io.WriteString(w, fmt.Sprintf(`{"cookie": "%s"}`, cookie))
}
func TestRequest_AddCookie(t *testing.T) {
client := NewClient(t).
To(Mux)
client.
Get("/request/cookie").
Test().
StatusForbidden()
client.
Get("/request/cookie").
AddCookie(&testCookie).
Test().
StatusOK().
JSON().
String("cookie", testCookie.String())
}
Calling *Request.Test will test the mock server and return a *Response.
You must have called Client.To or Client.ToFunc, otherwise causing a panic (htest.MockNilError)
// request_test.go
func TestRequest_Test(t *testing.T) {
defer func() {
assert.Equal(t, MockNilError, recover())
}()
NewClient(t).
Get("/request/header").
SetHeader(HeaderContentType, MIMEApplicationForm).
Test().
StatusBadRequest()
}
Calling *Request.Send will send a real http request and return a *Response
// request_test.go
func TestRequest_Send(t *testing.T) {
NewClient(t).
Get("https://api.github.com/users/Hexilee").
Send().
StatusOK().
JSON().
String("login", "Hexilee")
}
As *http.Request is embedded in htest.Request, you can regard *htest.Request as *http.Request. Just like:
userAgent := NewClient(t).
Get("https://api.github.com/users/Hexilee").
UserAgent()
Assert Response.StatusCode
*Response.Code(statusCode int)
// response_test.go
var (
ResponseCodeServer = chi.NewRouter()
)
func init() {
ResponseCodeServer.Get("/response/statusCode/{code}", StatusHandler)
}
func StatusHandler(w http.ResponseWriter, req *http.Request) {
codeStr := chi.URLParam(req, "code")
code, err := strconv.Atoi(codeStr)
if err != nil {
w.WriteHeader(http.StatusNotFound)
return
}
w.WriteHeader(code)
}
func TestResponse_Code(t *testing.T) {
NewClient(t).
To(ResponseCodeServer).
Get(fmt.Sprintf("/response/statusCode/%d", http.StatusBadRequest)).
Test().
Code(http.StatusBadRequest)
}
For more ergonomic development, *htest.Response has many methods to assert all the StatusCode in net/http
// response_test.go
func TestResponse_StatusContinue(t *testing.T) {
NewClient(t).
To(ResponseCodeServer).
Get(fmt.Sprintf("/response/statusCode/%d", http.StatusContinue)).
Test().
StatusContinue()
}
Assert Response.Headlers
*Response.Headers(key, expect string)
// response_test.go
var (
ResponseHeadersServer = chi.NewRouter()
)
func init() {
ResponseHeadersServer.Get("/response/headers", HeadersHandler)
}
func HeadersHandler(w http.ResponseWriter, req *http.Request) {
query := req.URL.Query()
header := query.Get("header")
value := query.Get("value")
w.Header().Set(header, value)
}
func TestResponse_Headers(t *testing.T) {
url := fmt.Sprintf("/response/headers?header=%s&value=%s", HeaderContentType, MIMEApplicationJSON)
NewClient(t).
To(ResponseHeadersServer).
Get(url).
Test().
Headers(HeaderContentType, MIMEApplicationJSON)
}
For more ergonomic development, *htest.Response has many methods to assert all the Headers in const.go
// response_test.go
func TestResponse_HeaderAccept(t *testing.T) {
url := fmt.Sprintf("/response/headers?header=%s&value=%s", HeaderAccept, "htest")
NewClient(t).
To(ResponseHeadersServer).
Get(url).
Test().
HeaderAccept("htest")
}
You can assert data in body straightly.
// server_test.go
Mux.Get("/body/user", UserDataHandler)
func UserDataHandler(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, UserData)
}
// response_test.go
const (
UserData = `{
"id": 1,
"name": "hexi"
}`
)
func TestResponse_Expect(t *testing.T) {
NewClient(t).
To(Mux).
Get("/body/user").
Test().
StatusOK().
Expect(UserData)
}
You can get data in body straightly
- String
// response_test.go
func TestResponse_String(t *testing.T) {
assert.Equal(t, UserData, NewClient(t).
To(Mux).
Get("/body/user").
Test().
StatusOK().
String())
}
- Bytes
// response_test.go
func TestResponse_Bytes(t *testing.T) {
assert.Equal(t, []byte(UserData), NewClient(t).
To(Mux).
Get("/body/user").
Test().
StatusOK().
Bytes())
}
If type of data in body is JSON, you can unmarshal it straightly
// response_test.go
type (
User struct {
Id uint
Name string
}
)
func TestResponse_Bind(t *testing.T) {
user := new(User)
NewClient(t).
To(Mux).
Get("/body/user").
Test().
StatusOK().
Bind(user)
assert.Equal(t, user.Id, uint(1))
assert.Equal(t, user.Name, "hexi")
}
You can return data in 4 types
As *http.Response is embedded in htest.Response, you can regard *htest.Response as *http.Response. Just like:
assert.Equal(t, "HTTP/1.1", NewClient(t).
To(Mux).
Get("/body/user").
Test().
Proto
)
htest provide 4 types of data to be returned
data as JSON
- Exist(key string)
- NotExist(key string)
- String(key, expect string)
- Int(key string, expect int64)
- True(key string)
- False(key string)
- Uint(key string, expect uint64)
- Time(key string, expect time.Time)
- Float(key string, expect float64)
// body_test.go
func TestJSON_Exist(t *testing.T) {
NewClient(t).
To(Mux).
Get("/name").
Test().
StatusOK().
JSON().
Exist("name").
NotExist("stuid")
}
func TestJSON_String(t *testing.T) {
user := new(User)
NewClient(t).
To(Mux).
Get("/body/user").
Test().
StatusOK().
JSON().
String("name", "hexi)
}
func TestJSON_NotEmpty(t *testing.T) {
user := new(User)
NewClient(t).
To(Mux).
Get("/body/user").
Test().
StatusOK().
JSON().
NotEmpty()
}
// body_test.go
type (
User struct {
Id uint
Name string
}
)
func TestJSON_Bind(t *testing.T) {
user := new(User)
NewClient(t).
To(Mux).
Get("/body/user").
Test().
StatusOK().
JSON().
Bind(user)
assert.Equal(t, user.Id, uint(1))
assert.Equal(t, user.Name, "hexi")
}
Same as JSON.
For more examples, you can find them in body_test.go
// body_test.go
func TestMD5_Expect(t *testing.T) {
NewClient(t).
To(Mux).
Get("/body/user").
Test().
StatusOK().
MD5().
Expect(UserDataMD5)
}
hash := NewClient(t).
To(Mux).
Get("/body/user").
Test().
StatusOK().
MD5().
Body()
Same as MD5.
For more examples, you can find them in body_test.go
There are many constants of header or header value in const.go
// const.go
package htest
// HTTP methods
const (
CONNECT = "CONNECT"
DELETE = "DELETE"
GET = "GET"
HEAD = "HEAD"
OPTIONS = "OPTIONS"
PATCH = "PATCH"
POST = "POST"
PUT = "PUT"
TRACE = "TRACE"
)
// MIME types
const (
MIMEApplicationJSON = "application/json"
MIMEApplicationJSONCharsetUTF8 = MIMEApplicationJSON + "; " + charsetUTF8
MIMEApplicationJavaScript = "application/javascript"
MIMEApplicationJavaScriptCharsetUTF8 = MIMEApplicationJavaScript + "; " + charsetUTF8
MIMEApplicationXML = "application/xml"
MIMEApplicationXMLCharsetUTF8 = MIMEApplicationXML + "; " + charsetUTF8
MIMETextXML = "text/xml"
MIMETextXMLCharsetUTF8 = MIMETextXML + "; " + charsetUTF8
MIMEApplicationForm = "application/x-www-form-urlencoded"
MIMEApplicationProtobuf = "application/protobuf"
MIMEApplicationMsgpack = "application/msgpack"
MIMETextHTML = "text/html"
MIMETextHTMLCharsetUTF8 = MIMETextHTML + "; " + charsetUTF8
MIMETextPlain = "text/plain"
MIMETextPlainCharsetUTF8 = MIMETextPlain + "; " + charsetUTF8
MIMEMultipartForm = "multipart/form-data"
MIMEOctetStream = "application/octet-stream"
)
const (
charsetUTF8 = "charset=UTF-8"
)
// Headers
const (
HeaderAccept = "Accept"
HeaderAcceptEncoding = "Accept-Encoding"
HeaderAllow = "Allow"
HeaderAuthorization = "Authorization"
HeaderContentDisposition = "Content-Disposition"
HeaderContentEncoding = "Content-Encoding"
HeaderContentLength = "Content-Length"
HeaderContentType = "Content-Type"
HeaderCookie = "Cookie"
HeaderSetCookie = "Set-Cookie"
HeaderIfModifiedSince = "If-Modified-Since"
HeaderLastModified = "Last-Modified"
HeaderLocation = "Location"
HeaderUpgrade = "Upgrade"
HeaderVary = "Vary"
HeaderWWWAuthenticate = "WWW-Authenticate"
HeaderXForwardedFor = "X-Forwarded-For"
HeaderXForwardedProto = "X-Forwarded-Proto"
HeaderXForwardedProtocol = "X-Forwarded-Protocol"
HeaderXForwardedSsl = "X-Forwarded-Ssl"
HeaderXUrlScheme = "X-Url-Scheme"
HeaderXHTTPMethodOverride = "X-HTTP-Method-Override"
HeaderXRealIP = "X-Real-IP"
HeaderXRequestID = "X-Request-ID"
HeaderServer = "Server"
HeaderOrigin = "Origin"
// Access control
HeaderAccessControlRequestMethod = "Access-Control-Request-Method"
HeaderAccessControlRequestHeaders = "Access-Control-Request-Headers"
HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin"
HeaderAccessControlAllowMethods = "Access-Control-Allow-Methods"
HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers"
HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials"
HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers"
HeaderAccessControlMaxAge = "Access-Control-Max-Age"
// Security
HeaderStrictTransportSecurity = "Strict-Transport-Security"
HeaderXContentTypeOptions = "X-Content-Type-Options"
HeaderXXSSProtection = "X-XSS-Protection"
HeaderXFrameOptions = "X-Frame-Options"
HeaderContentSecurityPolicy = "Content-Security-Policy"
HeaderXCSRFToken = "X-CSRF-Token"
)