Celerity is built for Test Driven Development and was designed with testing in mind. The celeritytest package provides easy implementation of test servers and response validation.

Testing the pure endpoint function

You can test the route handler function directly with ease by creating a new context, calling the function and casting out its response data. If we had a route that looked like this:

struct User {
  FirstName string `json:"firstName"`
  LastName string `json:"lastNmae"`
}
func MyHandler(c celerity.Context) celerity.Response {
  // get a user
  user := User{
    FirstName: "Alice",
    LastName: "Alisson",
  }
  // respond with the data
  c.R(user)
}

We could test the MyHandler function directly like this:

func TestMyHandler(t *testing.T) {
  // create a new context
  c := celerity.NewContext()
  // get the response
  res := MyHandler(c)
  // cast and validate
  user := res.Data.(User)
  if user.FirstName != "Alice" {
    t.Errorf("first name property was not valid: %s", user.FirstName)
  }
}

URL and query string parameters

When directly testing route handlers that depend on URL or query string params you need to set them directly in the Context.

Setting URL parameters

func TestMyHandler(t *testing.T) {
  // create a new context
  c := celerity.NewContext()
  // set URL parameters
  c.URLParams = celerity.Params{
    "id": "1"
  }
  // get the response
  res := MyHandler(c)
  // cast and validate
  user := res.Data.(User)
  if user.FirstName != "Alice" {
    t.Errorf("first name property was not valid: %s", user.FirstName)
  }
}

Setting query parameters

func TestMyHandler(t *testing.T) {
  // create a new context
  c := celerity.NewContext()
  // set URL parameters
  c.QueryParams = celerity.Params{
    "id": "1"
  }
  // get the response
  res := MyHandler(c)
  // cast and validate
  user := res.Data.(User)
  if user.FirstName != "Alice" {
    t.Errorf("first name property was not valid: %s", user.FirstName)
  }
}

Testing routes using a test server

You can fully test a route, middleware and all interactions using a managed test server. Internally this uses the http.TestServer provided by the http package in the Go standard library.

As an example, we will test this server setup:

main.go

struct User {
  FirstName string `json:"firstName"`
  LastName string `json:"lastNmae"`
}
func getUser(c celerity.Context) celerity.Response {
  // get a user
  user := User{
    FirstName: "Alice",
    LastName: "Alisson",
  }
  // respond with the data
  c.R(user)
}

func Server() *celerity.Server {
  svr := celerity.New()
  svr.GET("/user", getUser)
  return svr
}

func main() {
  celerity.HandleCLI(Server)
}

main_test.go

func TestGetUser(t *testing.T) {
  res := celeritytest.Get("/user")
  if res.StatusCode != 200 {
    t.Errorf("non 200 status code: %d", res.StatusCode)
  }
  if ok, v := res.AssertString("data.firstName", "Alice"); !ok {
    t.Errorf("first name not correct in response: %s", v)
  }
}

Checking single response values

To validate single values in the response a number of assertion methods are available that check the value at a specific JSON path against a passed value.

Checking string values

resp := celeritytest.Get(svr, "/foo")
if ok, v := resp.AssertString("data.user.firstName", "Alice"); ok {
  t.Errorf("firstName not returned correctly: %s", v)
}

Checking int values

resp := celeritytest.Get(svr, "/foo")
if ok, v := resp.AssertString("data.user.age", 19); ok {
  t.Errorf("age not returned correctly: %d", v)
}

Checking array lengths

resp := celeritytest.Get(svr, "/foo")
if l := resp.GetLength("data.users"); l != 3 {
  t.Errorf("incorrect number of users: %d", l)
}

Checking for existence

resp := celeritytest.Get(svr, "/foo")
if l := resp.Exists("data.users.1.firstName"); l != 3 {
  t.Error("firstName not returned in first user")
}

Getting values from the response

The GetResult function lets you pull out a value from the response data by JSON path.

resp := celeritytest.Get(svr, "/foo")
firstName := resp.GetResult("user.firstName").String()
age := resp.GetResult("user.age").Int()

Validate entire responses at once

Structs with validation tags can be used to validate the entire response comprehensibly. This can be done by passing an already existing structure you are using that has validation tags, such as a model. Alternatively an anonymous struct with hardened validation tags can be used directly.

Anonymous struct validation

Assuming a response such as:

{
  "success": true,
  "error": "",
  "data": {
    "people": [
      {
        "firstName": "Alice",
        "lastName": "Alisson",
        "age": 19
      },
      {
        "firstName": "Beverly",
        "lastName": "Beaver",
        "age": 21
      }
    ]
  }
}

We might use a struct validation like this:

s := struct {
  Success bool   `json:"success" validate:"required"`
  Error   string `json:"error" validate:"isdefault"`
  Data    struct {
    People []struct {
      FirstName string `json:"firstName" validate:"required,alpha"`
      LastName  string `json:"lastName" validate:"required,alpha"`
      Age       int    `json:"age" validate:"numeric,required"`
    } `json:"people" validate:"required,len=2,dive"`
  } `json:"data" validate:"required"`
}{}
if err := resp.Validate(&s); err != nil {
  t.Error(err.Error())
}

This uses the go-playground validator internally. For information on the tags available check out their documentation at: https://godoc.org/gopkg.in/go-playground/validator.v9

Validating a section of the response

Its possible you might not want to validate the entire response but only a section of it. This can be done by using the ExtractAt function to pull out the relevant part of the response into a validation tagged structure.

Lets suppose you had a validated model:

struct User {
  FirstName string `validate:"required"` `json:"firstName"`
  LastName string `validate:"required"` `json:"lastName"`
}

Also, we will assume your response is wrapped in a data node, as it is by default.

{
  "data": {
    "someValue": "test",
    "user": {
      "firstName": "Alice",
      "lastName": "Alisson"
    }
  }
}

Testing this response might look like this:

m := models.User{}
if err := resp.VadliateAt("data.user", &m); err != nil {
  t.Error(err.Error())
}
last modified Thursday, June 21, 2018
Next in Guides
Basic Routing
Setting up routes and scopes
Celerity is maintained by 5Sigma. Source code is available at Github.