scala-testkit: getting a response as a Map[String, Any] instead of a case class

1,392 views
Skip to first unread message

Mario Camou

unread,
Nov 19, 2013, 12:58:50 PM11/19/13
to spray...@googlegroups.com
Hi,

I’m trying to test a service that returns a relatively complex JSON. Instead of making a case class just for the test, I’d like to parse the response into a Map and use Spec2’s Map matchers.

I tried doing the following:

Get("/status") ~> route ~> check {
val resp = responseAs[Map[String, Any]]
// Do some checks here
}

but I get the following when running the test:

Could not unmarshal response to type 'scala.collection.immutable.Map' for `responseAs` assertion: MalformedContent(Expected object but got JArray(List(JObject(List((network,JString(api_test2)), (name,JString(Station 1 - Net 2)), (latitude,JDouble(0.0)), (description,JString(Station 1 - Net 2 - always unavailable)), (longitude,JDouble(0.0)), (id,JString(0202020202020202)), (status,JString(closed)), // And so on and so forth…

I’m using Json4sJackson as my JSON parser, and these look like the internal Json4s classes.

I also tried the following (which is ugly):

val resp = responseAs[String]
reap must contain(""""id”:"12345678"””")

and I got the same error. changing ‘scala.collection.immutable.Map’ to ‘java.lang.String’

The only thing I got to work was:

val resp = response.entity.asString

but of course, doing String matching on a JSON response is ugly and tedious.

Any ideas? Is this somehow possible?

Thanks,
-Mario.

Mario Camou

unread,
Nov 19, 2013, 1:14:45 PM11/19/13
to spray...@googlegroups.com
Never mind, I just discovered org.specs2.matcher.JsonMatchers.

Thanks!

Mario Camou

unread,
May 3, 2014, 4:08:20 PM5/3/14
to spray...@googlegroups.com
Here I am again. For some of my tests the JsonMatchers are not enough. This is my test:

   "Return a single token with a single parameter" in {
      Get(s"/token/${token.id}") ~>
      route ~>
      check {
              val resp = responseAs[Map[String, Any]]
              resp must havePairs(
                                   "state" -> "active",
                                   "description" -> token.description,
                                   "groups" -> token.groups.map{ g => Map("id" -> g.id, "name" -> g.name) }.toSeq,
                                   "id" -> token.id,
                                   "maintenance" -> token.maintenance,
                                   "current_bikes" -> token.currentBikes.map { case (id, since) =>
                                     Map("id" -> id, "since" -> since)
                                                                             }
                                 )
            }
    }

and with Spray 1.3.1 I'm getting the following:

Could not unmarshal response to type 'scala.collection.immutable.Map' for `responseAs` assertion: MalformedContent(No information known about type,Some(org.json4s.package$MappingException: No information known about type))

Response was: HttpResponse(200 OK,HttpEntity(application/json; charset=UTF-8,{"state":"active","description":"Test api 1","maintenance":false,"groups":[{"id":"api_test","name":"API test group"}],"id":"0000000011111111","currentBikes":[],"since":"2014-05-03T20:00:52Z"}),List(),HTTP/1.1)
java.lang.Exception: Could not unmarshal response to type 'scala.collection.immutable.Map' for `responseAs` assertion: MalformedContent(No information known about type,Some(org.json4s.package$MappingException: No information known about type))

So again... how do I do a reponseAs[Map[String, Any]]?

Thanks!

Mario Camou

unread,
May 3, 2014, 5:24:23 PM5/3/14
to spray...@googlegroups.com
Got it, you can use responseAs[JObject].values (in the case of an Array, responseAs[JArray].arr)
-Mario
I want to change the world, but they won’t give me the source code

--
You received this message because you are subscribed to the Google Groups "spray.io User List" group.
To unsubscribe from this group and stop receiving emails from it, send an email to spray-user+...@googlegroups.com.
Visit this group at http://groups.google.com/group/spray-user.
To view this discussion on the web visit https://groups.google.com/d/msgid/spray-user/9d2227f9-0cc5-4fa0-bfb9-2fdcd4add775%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

David Pérez

unread,
May 6, 2014, 5:30:50 AM5/6/14
to spray...@googlegroups.com
I have a similar problem when running the following test:

"GET icono Por2 debe retornar simulacro icono" in {
test("2") {
status === OK
responseAs[Array[Byte]] === Array[Byte](1, 2, 3)
contentType === `image/jpeg`
}
}

and here is the exception I receive:

java.lang.Exception: Could not unmarshal response to type 'Array[byte]' for `responseAs` assertion: UnsupportedContentType(Expected 'application/json')


Which imports do I need to handle correctly Array[Byte]?

I've read all the doc about marshalling and unmarshalling: http://spray.io/documentation/1.2.1/spray-httpx/unmarshalling/

Thanks in advance for any help.

David

Johannes Rudolph

unread,
May 6, 2014, 5:37:09 AM5/6/14
to spray...@googlegroups.com
Hi David,

On Tue, May 6, 2014 at 11:30 AM, David Pérez
<david.pere...@gmail.com> wrote:
> java.lang.Exception: Could not unmarshal response to type 'Array[byte]' for
> `responseAs` assertion: UnsupportedContentType(Expected 'application/json')
>
>
> Which imports do I need to handle correctly Array[Byte]?

It seems you have implicits from `SprayJsonSupport` or similar
imported which prevents the usual unmarshaller for `Array[Byte]` from
being picked up. Either you remove that import or you can
alternatively try something like

response.as(ByteArrayUnmarshaller) === Right(...)

--
Johannes

-----------------------------------------------
Johannes Rudolph
http://virtual-void.net

David Pérez

unread,
May 6, 2014, 7:18:30 AM5/6/14
to spray...@googlegroups.com, johannes...@googlemail.com
Thanks Johannes,

You're right.
I had this as a package object:

package object rest extends SprayJsonSupport with DefaultJsonProtocol {
implicit val jsonPortal = jsonFormat4(Portal)
implicit val jsonNoticia = jsonFormat4(Noticia)
}

and I lose flexibility for the few cases where I don't use JSON.

I've created a separated object, that import when necessary in the routes that need it.

David Pérez

unread,
May 6, 2014, 7:39:29 AM5/6/14
to spray...@googlegroups.com, johannes...@googlemail.com
Now the problem I'm having is this:

case class Icon(bytes: Array[Byte], tipo: MediaType)

def icon(idPortal: String): Option[Icon]

val myRoute = path("icon") {
get {
icon(idPortal) match {
case None => complete(NotFound)
case Some(Icon(bytes, mime)) =>
respondWithMediaType(mime) {
complete(bytes)
}
}
}
}

In my test function, "bytes" is Array[Byte](1, 2, 3) and "mime" is `image/jpeg` and is being serialized as [ 1, 2, 3], even though I told it to use custom mime.  I suspect that is trying to use json.

Marshalling in Spray is powerful, but a little complicated for newbies.

Johannes Rudolph

unread,
May 6, 2014, 8:00:42 AM5/6/14
to David Pérez, spray...@googlegroups.com
Hi David,

On Tue, May 6, 2014 at 1:39 PM, David Pérez
<david.pere...@gmail.com> wrote:
> Now the problem I'm having is this:
>
> case class Icon(bytes: Array[Byte], tipo: MediaType)
>
> def icon(idPortal: String): Option[Icon]
>
> val myRoute = path("icon") {
> get {
> icon(idPortal) match {
> case None => complete(NotFound)
> case Some(Icon(bytes, mime)) =>
> respondWithMediaType(mime) {
> complete(bytes)
> }
> }
> }
> }
>
> In my test function, "bytes" is Array[Byte](1, 2, 3) and "mime" is
> `image/jpeg` and is being serialized as [ 1, 2, 3], even though I told it to
> use custom mime. I suspect that is trying to use json.

Yes, using respondWithMediaType doesn't change the behavior of
Marshalling in any way. You have basically two options: Either you
implement a custom `Marshaller[Icon]` (see Marshaller.of as an
example), this will then automatically support "proper" content-type
negotiation, meaning that the right error will be delivered if the
client isn't accepting the content-type you are providing. Or, if you
don't care for proper error reporting you can also just complete the
request with an HttpEntity with `HttpEntity(contentType, bytes)`.

> Marshalling in Spray is powerful, but a little complicated for newbies.

Actually, marshalling and Content-Type negotiation is also complicated
in HTTP so trying to hide at least some of the complexity should be a
good thing. Still, you may be right that things are not as easy to do
and as understandable as they could be.

David Pérez

unread,
May 6, 2014, 9:12:29 AM5/6/14
to spray...@googlegroups.com, David Pérez, johannes...@googlemail.com
Thanks Johannes once again.  
Now it works ok, and I understand better how Spray works.  :-)

One last question about this theme.  Is there any directive to force marshalling of a given content type?

I've searched all the standard directives and see none that does this.

Am Dienstag, 6. Mai 2014 14:00:42 UTC+2 schrieb Johannes Rudolph:
Hi David,

Johannes Rudolph

unread,
May 6, 2014, 9:14:26 AM5/6/14
to David Pérez, spray...@googlegroups.com
On Tue, May 6, 2014 at 3:12 PM, David Pérez
<david.pere...@gmail.com> wrote:
> Is there any directive to force marshalling of a given content type?

You mean in a way that ignores the client's Accept header? You can
always use `complete(HttpResponse(entity = HttpEntity(contentType,
content)))` to do this (but you usually shouldn't).
Reply all
Reply to author
Forward
0 new messages