Getting started
Testing
Test handlers without opening a network listener.
Testing
app.Test runs a request through the full application, including the
router, middleware, recovery and bindings, and returns the recorded
response without opening a port or starting a server.
func TestHello(t *testing.T) {
app := ply.New()
app.Get("/hello/:name", func(c *core.Context) {
c.Text(200, "hello "+c.Param("name"))
})
req := httptest.NewRequest(http.MethodGet, "/hello/ada", nil)
resp := app.Test(req)
if resp.StatusCode != 200 {
t.Fatalf("status = %d", resp.StatusCode)
}
body, _ := io.ReadAll(resp.Body)
if string(body) != "hello ada" {
t.Fatalf("body = %q", body)
}
}resp is a standard *http.Response, so headers, status and body are read
the usual way:
resp := app.Test(httptest.NewRequest(http.MethodPost, "/login", body))
resp.StatusCode
resp.Header.Get("Content-Type")
json.NewDecoder(resp.Body).Decode(&out)What it exercises
- the same router and compiled table as production,
- middleware chains, including
Abort, - panic recovery (a panicking handler yields
500), - request parsing for path params, query strings and JSON bodies.
Notes
- The request body is passed through
req.Body.req.Hostandreq.Headerare honored. - The response body is buffered, so it stays readable after
Testreturns. - The returned
http.Responseuseshttp.NoBodywhen the response is empty. - Calling
Testrepeatedly is cheap: the handler is built once and reused. Routes registered later still match, because the router rebuilds its table lazily. - Requests are executed synchronously, so
Testis safe to call from parallel tests.
app.Testlives in the main package and usesnet/httptypes, so it adds about 57 KB to the binary. No new modules are added, becausenet/httpis already in the graph through the fasthttp static file handler. It adds no allocations and no work to the request path. Applications that never call it are unaffected at runtime.

Ply