HTTP steps

Cornichon has a set of built-in steps for various HTTP calls and assertions on the response.

HTTP effects

head("http://superhero.io/daredevil")

get("http://superhero.io/daredevil").withParams(
  "firstParam" -> "value1",
  "secondParam" -> "value2")

delete("http://superhero.io/daredevil").withHeaders(("Authorization", "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="))

post("http://superhero.io/batman").withBody("JSON description of Batman goes here")

put("http://superhero.io/batman").withBody("JSON description of Batman goes here").withParams(
  "firstParam" -> "value1",
  "secondParam" -> "value2")

patch("http://superhero.io/batman").withBody("JSON description of Batman goes here")

Use addParams and addHeaders to append to existing parameters and headers instead of replacing them.

get("http://superhero.io/batman")
  .withParams("a" -> "1")
  .addParams("b" -> "2")
  .addHeaders(("X-Custom", "value"))

There is a built-in support for HTTP body defined as String, if you wish to use other types please check out the section Custom HTTP body type.

HTTP assertions

status.is(200)

status.isSuccess      // 2xx
status.isRedirect     // 3xx
status.isClientError  // 4xx
status.isServerError  // 5xx
headers.name("cache-control").isPresent

headers.contain("cache-control" -> "no-cache")

headers.name("cache_control").isAbsent

headers.hasSize(3)

save_header_value("cache_control" -> "my-cache-control-value")
body.is(
  """
  {
    "name": "Batman",
    "realName": "Bruce Wayne",
    "city": "Gotham city",
    "hasSuperpowers": false,
    "publisher":{
      "name":"DC",
      "foundationYear":1934,
      "location":"Burbank, California"
    }
  }
  """)

body.ignoring("city", "hasSuperpowers", "publisher.foundationYear", "publisher.location").is(
  """
  {
    "name": "Batman",
    "realName": "Bruce Wayne",
    "publisher":{
      "name":"DC"
    }
  }
  """)

body.whitelisting.is(
  """
  {
    "name": "Batman",
    "realName": "Bruce Wayne",
    "publisher":{
      "name":"DC"
    }
  }
  """)

Ignored keys and extractors are JsonPaths following the format a.b.c[index].d.

The index value is either:

JsonPath can also be used to only assert part of the response

body.path("city").is("Gotham city")

body.path("hasSuperpowers").is(false)

body.path("publisher.name").is("DC")

body.path("city").containsString("Gotham")

body.path("superheroes[*].name").is("""[ "Spiderman", "IronMan", "Superman", "GreenLantern", "Batman" ]""")

body.path("publisher.foundationYear").is(1934)

body.path("publisher.foundationYear").isPresent

body.path("publisher.foundationMonth").isAbsent

body.path("name").matchesRegex("B.*n".r)

body.path("publisher.foundationYear").isLessThan(2000)

body.path("publisher.foundationYear").isGreaterThan(1900)

body.path("publisher.foundationYear").isBetween(1900, 2000)

It is possible to handle null values, given the following response body { "data" : null }

body.path("data").isAbsent  //incorrect
body.path("data").isPresent //correct
body.path("data").isNull    //correct
body.path("data").isNotNull //incorrect

If one key of the path contains a "." it has to be wrapped with "`" to notify the parser.

body.path("`message.en`").isPresent

body.path("`message.fr`").isAbsent

To address a root array use $ followed by the index to access.


body.path("$[2].name")

If the endpoint returns a collection, assert response body has several options (ordered, ignoring and using data table)

body.asArray.inOrder.ignoringEach("city", "hasSuperpowers", "publisher").is(
  """
  [{
    "name": "Batman",
    "realName": "Bruce Wayne"
  },
  {
    "name": "Superman",
    "realName": "Clark Kent"
  }]
  """)

body.asArray.inOrder.ignoringEach("publisher").is(
 """
  |    name     |    realName    |     city      |  hasSuperpowers |
  | "Batman"    | "Bruce Wayne"  | "Gotham city" |      false      |
  | "Superman"  | "Clark Kent"   | "Metropolis"  |      true       |
 """)

body.asArray.hasSize(2)
body.asArray.size.is(2) //equivalent to above
body.asArray.size.isLessThan(3)
body.asArray.size.isGreaterThan(1)
body.asArray.size.isBetween(1, 3)

body.asArray.isEmpty

body.asArray.isNotEmpty

body.asArray.contains(
  """
  {
    "name": "Batman",
    "realName": "Bruce Wayne",
    "city": "Gotham city",
    "hasSuperpowers": false,
    "publisher":{
      "name":"DC",
      "foundationYear":1934,
      "location":"Burbank, California"
    }
  }
  """)

body.asArray.not_contains(
  """
  {
    "name": "Joker"
  }
  """)

body expects JSON content. When receiving non-JSON payloads, use body_raw which offers String like assertions.

body_raw.containsString("xml")

HTTP streams

When I open_sse(s"http://superhero.io/stream", takeWithin = 1.seconds).withParams("justName" -> "true")

Then assert body.asArray.hasSize(2)

Then assert body.is("""
  |   eventType      |    data     |  id  | retry | comment |
  | "superhero name" |  "Batman"   | null | null  |   null  |
  | "superhero name" | "Superman"  | null | null  |   null  |
""")

SSE streams are aggregated over a period of time in an array, therefore the previous array predicates can be re-used.

GraphQL support

Cornichon offers an integration with the library Sangria to propose convenient features to test GraphQL API.

import sangria.macros._

 When I query_gql("/<project-key>/graphql").withQuery(
    graphql"""
      query MyQuery {
        superheroes {
          results {
            name
            realName
            publisher {
              name
            }
          }
        }
      }
    """
    )

query_gql can also be used for mutation query.

all built-in steps accepting String input/output can also accept an alternative lightweight JSON format using the gqljson StringContext.

import com.github.agourlay.cornichon.json.CornichonJson._

And assert body.ignoring("city", "publisher").is(
  gqljson"""
  {
    name: "Batman",
    realName: "Bruce Wayne",
    hasSuperpowers: false
  }
  """)