Search code examples
scalaakkasprayspecs2

Custom spray rejection handler not working properly in tests


I am building some JSON HTTP services using spray and I am having some problems testing a RejectionHandler. If I start the application running the command sbt run and make the request, the RejectionHandler process the MalformedRequestContentRejection as expected but I am getting an IllegalArgumentException when running the tests even with the route sealed. In the other hand, the MethodRejection works fine. The JSON validation is done using require

The next example is based in the spray-template repository branch on_spray-can_1.3_scala-2.11 with a POST endpoint and the new tests. I've made a fork with the entire example here

Notice the use of a case clase for deserialize JSONs, the use of the require method for validation and the declaration of an implicit RejectionHandler.

package com.example

import akka.actor.Actor
import spray.routing._
import spray.http._
import StatusCodes._
import MediaTypes._
import spray.httpx.SprayJsonSupport._

class MyServiceActor extends Actor with MyService {
  def actorRefFactory = context
  def receive = runRoute(myRoute)
}

case class SomeReq(field: String) {
  require(!field.isEmpty, "field can not be empty")
}
object SomeReq {
  import spray.json.DefaultJsonProtocol._
  implicit val newUserReqFormat = jsonFormat1(SomeReq.apply)
}

trait MyService extends HttpService {
  implicit val myRejectionHandler = RejectionHandler {
    case MethodRejection(supported) :: _ => complete(MethodNotAllowed, supported.value)
    case MalformedRequestContentRejection(message, cause) :: _ => complete(BadRequest, "requirement failed: field can not be empty")
  }
  val myRoute =
    pathEndOrSingleSlash {
      post {
        entity(as[SomeReq]) { req =>
          {
            complete(Created, req)
          }
        }
      }
    }
}

This is are the test implemented using spray-testkit. The last one expects a BadRequest but the test fails with an IllegarArgumentException.

package com.example

import org.specs2.mutable.Specification
import spray.testkit.Specs2RouteTest
import spray.http._
import StatusCodes._
import spray.httpx.SprayJsonSupport._

class MyServiceSpec extends Specification with Specs2RouteTest with MyService {
  def actorRefFactory = system

  "MyService" should {
    "leave GET requests to other paths unhandled" in {
      Get("/kermit") ~> myRoute ~> check {
        handled must beFalse
      }
    }
    "return a MethodNotAllowed error for PUT requests to the root path" in {
      Put() ~> sealRoute(myRoute) ~> check {
        status should be(MethodNotAllowed)
        responseAs[String] === "POST"
      }
    }
    "return Created for POST requests to the root path" in {
      Post("/", new SomeReq("text")) ~> myRoute ~> check {
        status should be(Created)
        responseAs[SomeReq] === new SomeReq("text")
      }
    }
    /* Failed test. Throws IllegalArgumentException */
    "return BadRequest for POST requests to the root path without field" in {
      Post("/", new SomeReq("")) ~> sealRoute(myRoute) ~> check {
        status should be(BadRequest)
        responseAs[String] === "requirement failed: field can not be empty"
      }
    }
  }
}

I am missing something?

Thanks in advance!


Solution

  • Your SomeReq class is being eagerly instantiated in the Post("/", new SomeReq("")) request builder and the require method is invoked as soon as the class is instantiated.

    To get around this try using the following instead:

    import spray.json.DefaultJsonProtocol._
    Post("/", JsObject("field" → JsString(""))) ~> sealRoute(myRoute) ~> check {
      status should be(BadRequest)
      responseAs[String] === "requirement failed: field can not be empty"
    }