LogoPly
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.Host and req.Header are honored.
  • The response body is buffered, so it stays readable after Test returns.
  • The returned http.Response uses http.NoBody when the response is empty.
  • Calling Test repeatedly 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 Test is safe to call from parallel tests.

app.Test lives in the main package and uses net/http types, so it adds about 57 KB to the binary. No new modules are added, because net/http is 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.