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())
}
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())
}