Unit Test
A good project can’t be built without unit tests. To help users build good projects, hertz of course provides unit testing tools.
The principle is similar to that of golang httptest, both of them just execute ServeHTTP
without going through the network and return the response after execution.
Create RequestContext
func CreateUtRequestContext(method, url string, body *Body, headers ...Header) *app.RequestContext
CreateUtRequestContext
Return an app.RequestContext for testing purposes.
Function Signature:
func CreateUtRequestContext(method, url string, body *Body, headers ...Header) *app.RequestContext
Example Code:
import (
"bytes"
"testing"
"github.com/cloudwego/hertz/pkg/common/test/assert"
"github.com/cloudwego/hertz/pkg/common/ut"
)
func TestCreateUtRequestContext(t *testing.T) {
body := "1"
method := "PUT"
path := "/hey/dy"
headerKey := "Connection"
headerValue := "close"
ctx := ut.CreateUtRequestContext(method, path, &ut.Body{Body: bytes.NewBufferString(body), Len: len(body)},
ut.Header{Key: headerKey, Value: headerValue})
assert.DeepEqual(t, method, string(ctx.Method()))
assert.DeepEqual(t, path, string(ctx.Path()))
body1, err := ctx.Body()
assert.DeepEqual(t, nil, err)
assert.DeepEqual(t, body, string(body1))
assert.DeepEqual(t, headerValue, string(ctx.GetHeader(headerKey)))
}
Send Request
func PerformRequest(engine *route.Engine, method, url string, body *Body, headers ...Header) *ResponseRecorder
PerformRequest
The PerformRequest
function sends a constructed request to the specified engine without network transmission.
The url can be a standard relative path or an absolute path.
If you want to set a streaming request body, you can set engine.streamRequestBody to true through server.WithStreamBody(true)
or set the len of the body to -1.
This function returns the ResponseRecorder.
Function Signature:
func PerformRequest(engine *route.Engine, method, url string, body *Body, headers ...Header) *ResponseRecorder
Example Code:
import (
"bytes"
"context"
"testing"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/common/config"
"github.com/cloudwego/hertz/pkg/common/test/assert"
"github.com/cloudwego/hertz/pkg/common/ut"
"github.com/cloudwego/hertz/pkg/route"
)
func TestPerformRequest(t *testing.T) {
router := route.NewEngine(config.NewOptions([]config.Option{}))
router.GET("/hey/:user", func(ctx context.Context, c *app.RequestContext) {
user := c.Param("user")
assert.DeepEqual(t, "close", c.Request.Header.Get("Connection"))
c.Response.SetConnectionClose()
c.JSON(201, map[string]string{"hi": user})
})
w := ut.PerformRequest(router, "GET", "/hey/hertz", &ut.Body{bytes.NewBufferString("1"), 1},
ut.Header{"Connection", "close"})
resp := w.Result()
assert.DeepEqual(t, 201, resp.StatusCode())
assert.DeepEqual(t, "{\"hi\":\"hertz\"}", string(resp.Body()))
}
Receive Response
When executing the PerformRequest function, functions such as NewRecord, Header, Write, WriteHeader, Flush have already been called internally. The user only needs to call the Result function to obtain the returned protocol.Response object, and then perform unit testing.
func NewRecorder() *ResponseRecorder
func (rw *ResponseRecorder) Header() *protocol.ResponseHeader
func (rw *ResponseRecorder) Write(buf []byte) (int, error)
func (rw *ResponseRecorder) WriteString(str string) (int, error)
func (rw *ResponseRecorder) WriteHeader(code int)
func (rw *ResponseRecorder) Flush()
func (rw *ResponseRecorder) Result() *protocol.Response
ResponseRecorder
Used to record the response information of the handler, as follows:
type ResponseRecorder struct {
// Code is the HTTP response code set by WriteHeader.
//
// Note that if a Handler never calls WriteHeader or Write,
// this might end up being 0, rather than the implicit
// http.StatusOK. To get the implicit value, use the Result
// method.
Code int
// header contains the headers explicitly set by the Handler.
// It is an internal detail.
header *protocol.ResponseHeader
// Body is the buffer to which the Handler's Write calls are sent.
// If nil, the Writes are silently discarded.
Body *bytes.Buffer
// Flushed is whether the Handler called Flush.
Flushed bool
result *protocol.Response // cache of Result's return value
wroteHeader bool
}
NewRecorder
Return the initialized ResponseRecorder object.
Function Signature:
func NewRecorder() *ResponseRecorder
Header
Return ResponseRecorder.header.
Function Signature:
func (rw *ResponseRecorder) Header() *protocol.ResponseHeader
Write
Write data of type []byte to ResponseRecorder.Body.
Function Signature:
func (rw *ResponseRecorder) Write(buf []byte) (int, error)
WriteString
Write data of type string to ResponseRecorder.Body.
Function Signature:
func (rw *ResponseRecorder) WriteString(str string) (int, error)
WriteHeader
Set ResponseRecorder.Code and ResponseRecorder.header.SetStatusCode(code).
Function Signature:
func (rw *ResponseRecorder) WriteHeader(code int)
Flush
Implemented http.Flusher, set ResponseRecorder.Flushed to true.
Function Signature:
func (rw *ResponseRecorder) Flush()
Result
Return the response information generated by the handler.
The returned response information should at least include StatusCode, Header, Body, and optional Trailer. In the future, it will support returning more response information.
Function Signature:
func (rw *ResponseRecorder) Result() *protocol.Response
Work with biz handler
Assume you have a handler go file and a function called Ping()
:
package handler
import (
"context"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/common/utils"
)
// Ping .
func Ping(ctx context.Context, c *app.RequestContext) {
c.JSON(200, utils.H{
"message": "pong",
})
}
Now you can do some unit test directly to the Ping()
function:
package handler
import (
"bytes"
"testing"
"github.com/cloudwego/hertz/pkg/app/server"
"github.com/cloudwego/hertz/pkg/common/test/assert"
"github.com/cloudwego/hertz/pkg/common/ut"
)
func TestPerformRequest(t *testing.T) {
h := server.Default()
h.GET("/ping", Ping)
w := ut.PerformRequest(h.Engine, "GET", "/ping", &ut.Body{bytes.NewBufferString("1"), 1},
ut.Header{"Connection", "close"})
resp := w.Result()
assert.DeepEqual(t, 201, resp.StatusCode())
assert.DeepEqual(t, "{\"message\":\"pong\"}", string(resp.Body()))
}
Every time you change the Ping()
behavior, you don’t need to copy it to test file again and again.
For more examples, refer to the unit test file in pkg/common/ut.