0
votes

Suppose I would to test HTTP responses in Scala to make sure my web application returns them as required.

For example, one response is required to have "status code" = Ok, "content-type: application/json" and "content-length" headers and a valid JSON body, another one is required to have "status code" = Bad Request and an error message, etc.

Suppose I have defined a few functions to test status, header, and body

 def testStatus(status:Status): HttpResponse => Either[String, Status] = ...
 def doesHeaderExist(name: String): HttpResponse => Either[String, Header] = ...
 def testBody(body: Body): HttpResponse => Either[String, Body] = ...

 ... // etc.

Now I need to compose them to define a function to test a response.

I would like to do it in a monadic way, i.e. define a monadResponseTest[A]

case class ResponseTest[A](req: HttpResponse, ea: Either[String, A]) {
  def unit(resp: HttpResponse, a: A) = ...
  def flatMap(f: A => ResponseTest[B]): ResponseTest[B] = ... 
}

re-define the test functions to return ResponseTest and compose them with the flatMap to define functions to test entire response

  val testErrorResponse: HttpResponse => ResponseTest[HttpResponse] = ...
  val testJsonResponse: HttpResponse => ResponseTest[HttpResponse] = ...

Does it make sense ? How would you suggest implement it?

1

1 Answers

3
votes

I would make the actions something like this:

def doesHeaderExist(name: String): HttpResponse => Try[HttpResponse]
def testBoxy(contents: String): HttpResponse => Try[HttpResponse]

Try composes, so if it fails, then what you wind up with is either something that has your final type in a Success or the exception (and failing test) in a Failure.

val result = for{
 _1 <- doesHeaderExist("foo")(response)
 _2 <- testBody("bar")(_1)
} yield _2

result match{
  case Success(that) => //do stuff
  case Failure(ex) => //do stuff with the exception
}

And you handle the exception where it happened.