Cannot get Marshaller to work in Integration Test

I am converting a considerably sized API written in Spray to Akka-Http. I’ve gotten the main conversion done, however I need to get the tests all converted over as well so I can ensure the conversion was really a success from a functionality POV.

The API has a series of unit tests and integration tests for the routes, and its in the integration tests that I’m having an issue with. In the old project we used spray-client to feed requests to the API, and I have converted this over to Akka-Http.

The problem I’m facing is with Unmarshalling of the HttpResponse I get from a request. The error I’m receiving is:

Error:(32, 25) type mismatch;
 found   : akka.http.scaladsl.model.HttpResponse
 required: example.akka.http.User
    dto = waitOnResponse(Get(endpoint))

From the following test code:

TestApi.scala
class TestApi extends Specification {
  implicit val system = ActorSystem()
  
  def waitOnResponse(request: HttpRequest): HttpResponse = {
    Await.result(Http().singleRequest(request), Duration(20, "seconds"))
  }
  
  sequential
  
  val endpoint = "http://localhost:8080/user"
  val knownUuid = UUID.fromString("05ea4e86-c742-11e9-aa8c-2a2ae2dbcce4")
  var dto:User = null
  step {
    dto = waitOnResponse(Get(endpoint))
  }

  "calling GET /user" should {
    "return {meta:{status:OK}, user: User }" in {
      dto.copy(id = knownUuid) must beEqualTo(User(knownUuid, "Test", "Akka"))
    }
  }
  
}

One of the imports contains the spray-json converters:

ConversionHelper.scala
object ConversionHelper extends DefaultJsonProtocol with SprayJsonSupport  {

  implicit object UUIDJsonFormat extends JsonFormat[UUID] {
    def write(x: UUID) = {
      require(x ne null)
      JsString(x.toString)
    }
    def read(value: JsValue) = {
      def stringToUUID(s: String): UUID = Try(UUID.fromString(s))
        .recover { case t => deserializationError("Expected a valid UUID, but got " + s) }
        .get
      value match {
        case JsString(x) => stringToUUID(x)
        case x           => deserializationError("Expected UUID as JsString, but got " + x)
      }
    }
  }
  
  implicit val metaJsonFormat = jsonFormat3(Meta)

  implicit val emptyStatusResultJsonFormat = jsonFormat1(EmptyStatusResult)

  implicit val userFormat = jsonFormat3(User)
  implicit val userCreatedStatusResultJsonFormat = jsonFormat2(UserCreatedStatusResult)
  
  
}

I know when these tests were running under Spray, we did have some pretty funky glue-code written that I believe makes the marshalling for the tests work, that code looked like this:

Old Spray code from ConversionHelper.scala

  private def unmarshalStatusResult[T:Unmarshaller](response:HttpResponse): Deserialized[T] = {
    (Unmarshaller.unmarshal[T](response.entity)).left.map {
      case e@ContentExpected => e
      case e:MalformedContent => 
        MalformedContent(s"HttpResponse: ${response}  ErrorMessage: ${e.errorMessage}", e.cause)
      case e:UnsupportedContentType =>
        UnsupportedContentType(s"HttpResponse: ${response}  ErrorMessage: ${e.errorMessage}")
    }
  }

  def waitAndUnmarshal[T:Unmarshaller](request: HttpRequest): Deserialized[T] = {
    unmarshalStatusResult(waitOnResponse(request))
  }

I’m wondering how I can accomplish the same thing using Akka-Http?

I’ve created a working example of this problem here if that helps:

Any help would be greatly appreciated.

Craig

Looks like I managed to get this working. The SprayJsonSupport trait has this handy method: sprayJsonUnmarshallerConverter

I changed my ConversionHelper.scala file from:

  implicit val metaJsonFormat = jsonFormat3(Meta)
  
  implicit val emptyStatusResultJsonFormat = jsonFormat1(EmptyStatusResult)
  
  implicit val userFormat = jsonFormat3(User)
  
  implicit val userCreatedStatusResultJsonFormat = jsonFormat2(UserCreatedStatusResult)

to read:

  implicit val metaJsonFormat = jsonFormat3(Meta)
  implicit val metaUnmarshaller =
    sprayJsonUnmarshallerConverter[Meta](metaJsonFormat)
  
  implicit val emptyStatusResultJsonFormat = jsonFormat1(EmptyStatusResult)
  implicit val emptyStatusResultDtoUnmarshaller =
    sprayJsonUnmarshallerConverter[EmptyStatusResult](emptyStatusResultJsonFormat)
  
  implicit val userFormat = jsonFormat3(User)
  implicit val userUnmarsharller =
    sprayJsonUnmarshallerConverter[User](userFormat)
  
  implicit val userCreatedStatusResultJsonFormat = jsonFormat2(UserCreatedStatusResult)
  implicit val userCreatedStatusResultUnmarshaller: FromEntityUnmarshaller[UserCreatedStatusResult] =
    sprayJsonUnmarshallerConverter[UserCreatedStatusResult](userCreatedStatusResultJsonFormat)

Adding in all those unmarshaller statements fixed things up. My guess is when you’re running Akka-Http from the routing side, it’s automatically doing this for you. So from the client-side you have to replicate this.

When I changed my test to read like the following, everything worked.

dto = Await.result(Unmarshal(waitOnResponse(Get(endpoint))).to[User], Duration("20s"))

I updated the repo to have a BROKEN branch, and master now has the working version.

Regards,

Craig