37

So I have a function with this signature (akka.http.model.HttpResponse):

def apply(query: Seq[(String, String)], accept: String): HttpResponse

I simply get a value in a test like:

val resp = TagAPI(Seq.empty[(String, String)], api.acceptHeader)

I want to check its body in a test something like:

resp.entity.asString == "tags"

My question is how I can get the response body as string?

tg44
  • 810
  • 1
  • 8
  • 21
  • Relevant: http://stackoverflow.com/q/31532838/390708 – Brian Aug 31 '15 at 16:36
  • 3
    Are you using `akka-http-testkit`? If yes, you can use `entityAs[String]` in the test to get the body as a String value. – jrudolph Sep 01 '15 at 09:47
  • I need to use PlaySpec so I cant use akka-http-testkit :( – tg44 Sep 02 '15 at 09:24
  • I noticed that `entityAs[String]` does not work if akka-http-circe's `FailFastCirceSupport` (1.22.0) was brought into context. Work-around was to move the import. – akauppi Dec 01 '18 at 20:37

7 Answers7

42
import akka.http.scaladsl.unmarshalling.Unmarshal

implicit val system = ActorSystem("System")  
implicit val materializer = ActorFlowMaterializer() 

val responseAsString: Future[String] = Unmarshal(entity).to[String]
Andrii Abramov
  • 10,019
  • 9
  • 74
  • 96
user3548738
  • 556
  • 5
  • 6
  • Which akka-http-core version are you using for that? With 2.4.6, I don't find the `akka.http.scaladsl.unmarshalling`, anywhere. – akauppi May 24 '16 at 11:08
  • 1
    @akauppi this is `akka-http-experimental` not `akka-http-core` – wlk Jul 12 '16 at 12:37
  • 1
    Unfortunately `Unmarshal` lives in `akka-http` artifact (not `akka-http-core` or `akka-http-client`). – Tvaroh Aug 13 '18 at 18:52
  • 1
    Think it should be ActorMaterializer() rather than ActorFlowMaterializer()? – user79074 Oct 31 '19 at 19:58
30

Since Akka Http is streams based, the entity is streaming as well. If you really need the entire string at once, you can convert the incoming request into a Strict one:

This is done by using the toStrict(timeout: FiniteDuration)(mat: Materializer) API to collect the request into a strict entity within a given time limit (this is important since you don't want to "try to collect the entity forever" in case the incoming request does actually never end):

import akka.stream.ActorFlowMaterializer
import akka.actor.ActorSystem

implicit val system = ActorSystem("Sys") // your actor system, only 1 per app
implicit val materializer = ActorFlowMaterializer() // you must provide a materializer

import system.dispatcher
import scala.concurrent.duration._
val timeout = 300.millis

val bs: Future[ByteString] = entity.toStrict(timeout).map { _.data }
val s: Future[String] = bs.map(_.utf8String) // if you indeed need a `String`
Konrad 'ktoso' Malawski
  • 13,102
  • 3
  • 47
  • 52
12

You can also try this one also.

responseObject.entity.dataBytes.runFold(ByteString(""))(_ ++ _).map(_.utf8String) map println
Shashank Jain
  • 559
  • 7
  • 9
5
Unmarshaller.stringUnmarshaller(someHttpEntity)

works like a charm, implicit materializer needed as well

5

Here is simple directive that extracts string from request's body

def withString(): Directive1[String] = {
  extractStrictEntity(3.seconds).flatMap { entity =>
    provide(entity.data.utf8String)
  }
}
Andrii Abramov
  • 10,019
  • 9
  • 74
  • 96
Alexander Kondaurov
  • 3,677
  • 5
  • 42
  • 64
4

Unfortunately in my case, Unmarshal to String didn't work properly complaining on: Unsupported Content-Type, supported: application/json. That would be more elegant solution, but I had to use another way. In my test I used Future extracted from entity of the response and Await (from scala.concurrent) to get the result from the Future:

Put("/post/item", requestEntity) ~> route ~> check {
  val responseContent: Future[Option[String]] =
  response.entity.dataBytes.map(_.utf8String).runWith(Sink.lastOption)

  val content: Option[String] = Await.result(responseContent, 10.seconds)
  content.get should be(errorMessage)
  response.status should be(StatusCodes.InternalServerError)
}

If you need to go through all lines in a response, you can use runForeach of Source:

 response.entity.dataBytes.map(_.utf8String).runForeach(data => println(data))
Andrii Abramov
  • 10,019
  • 9
  • 74
  • 96
Shendor
  • 749
  • 2
  • 11
  • 18
  • This solution works for me even though I'm extending `FailFastCirceSupport`. Some of the other answers that rely on `Unmarshal(…).to[String]` didn't work, probably because I'm extending that trait. – Daniel Werner Mar 31 '22 at 09:25
1

Here is my working example,

  import akka.actor.ActorSystem
  import akka.http.scaladsl.Http
  import akka.http.scaladsl.model._
  import akka.stream.ActorMaterializer
  import akka.util.ByteString

  import scala.concurrent.Future
  import scala.util.{ Failure, Success }

  def getDataAkkaHTTP:Unit = {

    implicit val system = ActorSystem()
    implicit val materializer = ActorMaterializer()
    // needed for the future flatMap/onComplete in the end
    implicit val executionContext = system.dispatcher

    val url = "http://localhost:8080/"
    val responseFuture: Future[HttpResponse] = Http().singleRequest(HttpRequest(uri = url))

    responseFuture.onComplete {
      case Success(res) => {
        val HttpResponse(statusCodes, headers, entity, _) = res
        println(entity)
        entity.dataBytes.runFold(ByteString(""))(_ ++ _).foreach (body => println(body.utf8String))
        system.terminate()
      }
      case Failure(_) => sys.error("something wrong")
    }


  }
Charlie 木匠
  • 2,234
  • 19
  • 19