Skip to main content

Testing

Testing Handler

GET /users/:id

Handler below retrieves user by id from the database. If user is not found it returns 404 error with a message.

CreateUser

POST /users

  • Accepts JSON payload
  • On success 201 - Created
  • On error 500 - Internal Server Error

GetUser

GET /users/:email

  • On success 200 - OK
  • On error 404 - Not Found if user is not found otherwise 500 - 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 (
"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{
"[email protected]": &User{"Jon Snow", "[email protected]"},
}
userJSON = `{"name":"Jon Snow","email":"[email protected]"}`
)

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 := &controller{mockDB}

// Assertions
if assert.NoError(t, h.createUser(c)) {
assert.Equal(t, http.StatusCreated, rec.Code)
assert.Equal(t, userJSON, rec.Body.String())
}
}

// Same test as above but using `echotest` package helpers
func TestCreateUserWithEchoTest(t *testing.T) {
c, rec := echotest.ContextConfig{
Headers: map[string][]string{
echo.HeaderContentType: {echo.MIMEApplicationJSON},
},
JSONBody: []byte(`{"name":"Jon Snow","email":"[email protected]"}`),
}.ToContextRecorder(t)

h := &controller{mockDB}

// Assertions
if assert.NoError(t, h.createUser(c)) {
assert.Equal(t, http.StatusCreated, rec.Code)
assert.Equal(t, userJSON+"\n", rec.Body.String())
}
}

// Same test as above but even shorter
func TestCreateUserWithEchoTest2(t *testing.T) {
h := &controller{mockDB}

rec := echotest.ContextConfig{
Headers: map[string][]string{
echo.HeaderContentType: {echo.MIMEApplicationJSON},
},
JSONBody: []byte(`{"name":"Jon Snow","email":"[email protected]"}`),
}.ServeWithHandler(t, h.createUser)

assert.Equal(t, http.StatusCreated, rec.Code)
assert.Equal(t, userJSON+"\n", rec.Body.String())
}

func TestGetUser(t *testing.T) {
// Setup
e := echo.New()
req := httptest.NewRequest(http.MethodGet, "/", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)

c.SetPath("/users/:email")
c.SetPathValues(echo.PathValues{
{Name: "email", Value: "[email protected]"},
})
h := &controller{mockDB}

// Assertions
if assert.NoError(t, h.getUser(c)) {
assert.Equal(t, http.StatusOK, rec.Code)
assert.Equal(t, userJSON, rec.Body.String())
}
}

func TestGetUserWithEchoTest(t *testing.T) {
c, rec := echotest.ContextConfig{
PathValues: echo.PathValues{
{Name: "email", Value: "[email protected]"},
},
Headers: map[string][]string{
echo.HeaderContentType: {echo.MIMEApplicationJSON},
},
JSONBody: []byte(userJSON),
}.ToContextRecorder(t)

h := &controller{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 Form Payload

// import "net/url"
f := make(url.Values)
f.Set("name", "Jon Snow")
f.Set("email", "[email protected]")
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(f.Encode()))
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationForm)

Multipart form payload:

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 Params

c.SetPathValues(echo.PathValues{
{Name: "id", Value: "1"},
{Name: "email", Value: "[email protected]"},
})

Setting Query Params

// import "net/url"
q := make(url.Values)
q.Set("email", "[email protected]")
req := httptest.NewRequest(http.MethodGet, "/?"+q.Encode(), nil)

Testing Middleware

func TestCreateUserWithEchoTest2(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{
PathValues: echo.PathValues{{Name: "email", Value: "[email protected]"}},
}.ToContextRecorder(t)

err := middleware(handler)(c)
if err != nil {
t.Fatal(err)
}
// check that middleware set the value
userID, err := echo.ContextGet[int64](c, "user_id")
assert.NoError(t, err)
assert.Equal(t, int64(1234), userID)

// check that handler returned the correct response
assert.Equal(t, http.StatusTeapot, rec.Code)
}

For now you can look into built-in middleware test cases.