REST API Testing with Scalatest - Matchers
| Reading time: 4 minutes.
In the previous post I’ve described how to start with test automation of the REST API using ScalaTest. Today I would like to show some more examples of using ScalaTest matchers - a smart way of writing more readable assertions.
String matchers
Let’s get back for a while to our previous code example. Our test seems still a bit unfinished as we have no assertions on the response body. ScalaTest has few methods defined called matchers which will give us nice looking assertions on the different data types.
startWith, endWith, include
First, we’ll check simple assertions working on Strings:
The response body must contain the blog name:
response.body must include("https://hiquality.dev")
The body must also start and end with braces:
response.body must startWith("{")
response.body must endWith("}")
Regular expressions
We can also check if the response body matches a regular expression:
response.body must fullyMatch regex """.*"blog":"(https://)?hiquality\.dev".*"""
Parsing a response to JSON
Checking the response body as string might be a bit too verbose. Of course there is a better way to do it: we’ll parse the response body to a Scala object.
In this example I’ve chosen Circe - one of the most popular JSON libraries for Scala.
Let’s add some dependencies to build.sbt
:
val circeVersion = "0.14.6"
libraryDependencies += "io.circe" %% "circe-core" % circeVersion
libraryDependencies += "io.circe" %% "circe-generic" % circeVersion
libraryDependencies += "io.circe" %% "circe-parser" % circeVersion
We’ll use also sttp’s Circe integration:
libraryDependencies += "com.softwaremill.sttp.client3" %% "circe" % "3.9.3"
First we’ll define a GitHubUserProfile
case class:
case class GitHubUserProfile(
login: String,
id: Int,
name: String,
company: Option[String],
blog: String
)
and pass it as a type parameter to the decoder in the request definition:
val request = quickRequest
.get(uri"https://api.github.com/users/kkomorowski")
.response(asJson[GitHubUserProfile])
In the previous example the response body had type String
.
This time it is different and - the client returns
Either[ResponseException[String, circe.Error], GitHubUserProfile]
.
If parsing the response body fails, we’ll get an exception on the
left side of the Either
, otherwise we’ll get the parsed object on the right side.
We can now check if the response was successful and safely extract the value from the Either
and assert on the fields of the object:
response.body.isRight must be(true)
val user = response.body.toOption.get
user.blog must be("https://hiquality.dev")
The problem with this code snippet is when the first assertion fails, we’ll get quite unpleasant error on the logs:
false was not equal to true
ScalaTestFailureLocation: GitHubUserSpec at (GitHubUserSpec.scala:49)
Expected :true
Actual :false
Except for the file and line number in the stack trace this gives not much
clue what was the reason for failure. If we skip the assertion here, we could
end up in even more cumbersome NoSuchElementException
.
EitherValues trait
Fortunately ScalaTest provides a handy EitherValues
trait that makes
it easier to work with Either
values.
Let’s import it and use it in our test:
import org.scalatest.EitherValues
and mix-in it by extending our test class.
We can now write our previous assertion the following way:
val user = response.body.value
user.blog must be("https://hiquality.dev")
The value
implicit conversion is used to extract the value
from the Either
and to assert on it.
We can also assert on the left side of the Either
if the parsing of the response body fails:
response.body.left.value mustBe a [ResponseException[_, _]]
In the example above we’ve asserted not on the value but on the type of the left side of the Either
.
Similar traits as EitherValues
exist for Option
and Try
types.
Matcher negations
We can also easily negate the matcher by using a not
word in our matcher.
user.blog must not be empty
Conclusion
In this blog post we’ve learned how to parse an HTTP response body to
Scala case class using Circe. We’ve tried different ways of writing
the assertions in the test and have more readable error messages
for the failed assertions thanks to EitherValue
trait provided by
ScalaTest.
Thanks for reading this article and see you in the next episode of ScalaTest series.