Testing
Echo handlers and middleware are plain functions over an echo.Context, so they are
straightforward to test with the standard net/http/httptest package. The
echotest package provides helpers that cut down on boilerplate.
Testing a handler
Section titled “Testing a handler”Consider two handlers:
CreateUser — POST /users
- Accepts a JSON payload.
- Returns
201 Createdon success. - Returns
500 Internal Server Erroron error.
GetUser — GET /users/:email
- Returns
200 OKon success. - Returns
404 Not Foundif the user does not exist, otherwise500 Internal Server Error.
handler.go:
package handler
import ( "net/http"
"github.com/labstack/echo/v5")
type ( User struct { Name string `json:"name" form:"name"` Email string `json:"email" form:"email"` } handler struct { db map[string]*User })
func (h *handler) createUser(c *echo.Context) error { u := new(User) if err := c.Bind(u); err != nil { return err } return c.JSON(http.StatusCreated, u)}
func (h *handler) getUser(c *echo.Context) error { email := c.Param("email") user := h.db[email] if user == nil { return echo.NewHTTPError(http.StatusNotFound, "user not found") } return c.JSON(http.StatusOK, user)}handler_test.go:
package handler
import ( "fmt" "net/http" "net/http/httptest" "strings" "testing"
"github.com/labstack/echo/v5" "github.com/labstack/echo/v5/echotest" "github.com/stretchr/testify/assert")
var ( mockDB = map[string]*User{ })
func TestCreateUser(t *testing.T) { // Setup e := echo.New() req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(userJSON)) req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
rec := httptest.NewRecorder() c := e.NewContext(req, rec)
h := &handler{mockDB}
// Assertions if assert.NoError(t, h.createUser(c)) { assert.Equal(t, http.StatusCreated, rec.Code) assert.Equal(t, userJSON+"\n", rec.Body.String()) }}Using the echotest helpers
Section titled “Using the echotest helpers”echotest.ContextConfig builds a context (and recorder) from a declarative
description of the request:
// Same test as above, using echotest.func TestCreateUserWithEchoTest(t *testing.T) { c, rec := echotest.ContextConfig{ Headers: map[string][]string{ echo.HeaderContentType: {echo.MIMEApplicationJSON}, }, JSONBody: []byte(userJSON), }.ToContextRecorder(t)
h := &handler{mockDB}
// Assertions if assert.NoError(t, h.createUser(c)) { assert.Equal(t, http.StatusCreated, rec.Code) assert.Equal(t, userJSON+"\n", rec.Body.String()) }}
// Even shorter, using ServeWithHandler.func TestCreateUserWithServeHandler(t *testing.T) { h := &handler{mockDB}
rec := echotest.ContextConfig{ Headers: map[string][]string{ echo.HeaderContentType: {echo.MIMEApplicationJSON}, }, JSONBody: []byte(userJSON), }.ServeWithHandler(t, h.createUser)
assert.Equal(t, http.StatusCreated, rec.Code) assert.Equal(t, userJSON+"\n", rec.Body.String())}
func TestGetUser(t *testing.T) { c, rec := echotest.ContextConfig{ PathValues: echo.PathValues{ }, Headers: map[string][]string{ echo.HeaderContentType: {echo.MIMEApplicationJSON}, }, }.ToContextRecorder(t)
h := &handler{mockDB}
// Assertions if assert.NoError(t, h.getUser(c)) { assert.Equal(t, http.StatusOK, rec.Code) assert.Equal(t, userJSON+"\n", rec.Body.String()) }}Using a form payload
Section titled “Using a form payload”// import "net/url"f := make(url.Values)f.Set("name", "Jon Snow")req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(f.Encode()))req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationForm)A multipart form payload with echotest:
func TestContext_MultipartForm(t *testing.T) { testConf := echotest.ContextConfig{ MultipartForm: &echotest.MultipartForm{ Fields: map[string]string{ "key": "value", }, Files: []echotest.MultipartFormFile{ { Fieldname: "file", Filename: "test.json", Content: echotest.LoadBytes(t, "testdata/test.json"), }, }, }, } c := testConf.ToContext(t)
assert.Equal(t, "value", c.FormValue("key")) assert.Equal(t, http.MethodPost, c.Request().Method) assert.Equal(t, true, strings.HasPrefix(c.Request().Header.Get(echo.HeaderContentType), "multipart/form-data; boundary="))
fv, err := c.FormFile("file") if err != nil { t.Fatal(err) } assert.Equal(t, "test.json", fv.Filename)}Setting path parameters
Section titled “Setting path parameters”c.SetPathValues(echo.PathValues{ {Name: "id", Value: "1"},})Setting query parameters
Section titled “Setting query parameters”// import "net/url"q := make(url.Values)req := httptest.NewRequest(http.MethodGet, "/?"+q.Encode(), nil)Testing middleware
Section titled “Testing middleware”func TestMiddleware(t *testing.T) { handler := func(c *echo.Context) error { return c.JSON(http.StatusTeapot, fmt.Sprintf("email: %s", c.Param("email"))) } middleware := func(next echo.HandlerFunc) echo.HandlerFunc { return func(c *echo.Context) error { c.Set("user_id", int64(1234)) return next(c) } }
c, rec := echotest.ContextConfig{ }.ToContextRecorder(t)
if err := middleware(handler)(c); err != nil { t.Fatal(err) }
// Check that the middleware set the value. userID, err := echo.ContextGet[int64](c, "user_id") assert.NoError(t, err) assert.Equal(t, int64(1234), userID)
// Check that the handler returned the correct response. assert.Equal(t, http.StatusTeapot, rec.Code)}