Ruby Cucumber codebase • Lack of Ruby expertise • Difficulty to scale a DSL based on String parsing • Lack of enthusiasm for writing acceptance tests • Frustration breeds creativity… 2
the next 50 slides afterwards will! • No boring domain as example please… • Let’s play cards! • My first feature using “http://deckofcardsapi.com” • Source for later reference partly at https://github.com/agourlay/ cornichon/blob/master/src/it/scala/ com.github.agourlay.cornichon.examples/DeckOfCard.scala 5
and PATCH • Use a builder shape with body, params, headers for consistency • Params in URL are supported for convenience • Body supported on every HTTP verb 12
type can be parametrised via typeclasses case class HttpRequest[A: Show: Resolvable: Encoder](body: Option[A],…) • Cats.Show / Circe.Encoder / Cornichon.Resolvable 14
city") Then assert body.path("city").containsString("Gotham") Then assert body.path("city").matchesRegex(“.*city”.r) Then assert body.path("country").isAbsent 19
{ When I get("http://superhero.io/batman") Then assert status.is(200) } RetryMax(3) { When I get("http://superhero.io/batman") Then assert status.is(200) } 23
Then assert body.path("hasSuperpowers").is(true) } Concurrently(factor = 3, maxTime = 10 seconds) { When I get(“http://superhero.io/batman") Then assert status.is(200) } 24
Then assert body.path("hasSuperpowers").is(true) } Concurrently(factor = 3, maxTime = 10 seconds) { When I get(“http://superhero.io/batman") Then assert status.is(200) } Within(maxDuration = 10 seconds) { When I get("http://superhero.io/batman") Then assert status.is(200) } 24
effect request body, parameters, URL, headers • JSON path, assertions • actually built-in…almost everywhere • In your custom steps via the Resolver API manually 26
random Integer • <random-string> for a random String of length 5 • <random-alphanum-string> for a random alphanumeric • <random-boolean> for a random Boolean string Built-in placeholders 28
random Integer • <random-string> for a random String of length 5 • <random-alphanum-string> for a random alphanumeric • <random-boolean> for a random Boolean string • <timestamp> for the current timestamp Built-in placeholders 28
assert status.is(200) } def random_superheroes_until(name: String) = Eventually(maxDuration = 3 seconds, interval = 10 millis) { When I get("/superheroes/random") Then assert body.path("name").is(name) Then I print_step("bingo!") } Then assert superhero_exists("Batman") 33
assert status.is(200) } def random_superheroes_until(name: String) = Eventually(maxDuration = 3 seconds, interval = 10 millis) { When I get("/superheroes/random") Then assert body.path("name").is(name) Then I print_step("bingo!") } Then assert random_superheroes_until("Batman") Then assert superhero_exists("Batman") 33
the JSON handling • Cats for the FP goodness • Parboiled2 for parsing (placeholder, JSON path, DT) • Scalatest for the SBT integration and test runner 38
• Macro collects elements of a given type • Contributed by @OlegIlyenko case class BodyElementCollector[Body, Result](fn: List[Body] ⇒ Result) { def apply(body: ⇒ Body): Result = macro Macro.collectImpl } 41
scenario • Macro collects scenarios to build the feature • Feature is turned into a Scalatest AsyncWordSpec • Each scenario becomes a test • Scalatest invokes the engine that runs the scenario 43
scenario • Macro collects scenarios to build the feature • Feature is turned into a Scalatest AsyncWordSpec • Each scenario becomes a test • Scalatest invokes the engine that runs the scenario • Result of the execution is fed into a Scalatest assert() 43
from the well-known Gherkin syntax • Important “classpath” (acceptance testing) • No beforeAllFeatures and afterAllFeatures • Obviously designed to fit our use case in first place 51
• Full documentation on a Readme file • Rely partly on exception internally (0.11 will hopefully fix it) • Truck factor of 1 • Makes acceptance tests so fun that you less write UT ;) 52