diff options
author | Adam Warski <adam@warski.org> | 2018-01-29 14:12:23 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-01-29 14:12:23 +0100 |
commit | 88a3a820c8233d335242e51936bc22723c391cd3 (patch) | |
tree | 7471e7705b333f92ad0699621b558ee93c3f610a | |
parent | b5e8aa36e439e96c1b99a83973a1b0fda23b1d15 (diff) | |
parent | 2f2c6b66adf8f2157f7c3cdf47d7d784ed105e87 (diff) | |
download | sttp-88a3a820c8233d335242e51936bc22723c391cd3.tar.gz sttp-88a3a820c8233d335242e51936bc22723c391cd3.tar.bz2 sttp-88a3a820c8233d335242e51936bc22723c391cd3.zip |
Merge pull request #61 from softwaremill/then-respond-future
Allow SttpBackendStub to accept monad with response
3 files changed, 71 insertions, 12 deletions
diff --git a/core/src/main/scala/com/softwaremill/sttp/testing/SttpBackendStub.scala b/core/src/main/scala/com/softwaremill/sttp/testing/SttpBackendStub.scala index 9b4e93e..a603894 100644 --- a/core/src/main/scala/com/softwaremill/sttp/testing/SttpBackendStub.scala +++ b/core/src/main/scala/com/softwaremill/sttp/testing/SttpBackendStub.scala @@ -28,7 +28,7 @@ import scala.util.{Failure, Success, Try} */ class SttpBackendStub[R[_], S] private ( rm: MonadError[R], - matchers: PartialFunction[Request[_, _], Response[_]], + matchers: PartialFunction[Request[_, _], R[Response[_]]], fallback: Option[SttpBackend[R, S]]) extends SttpBackend[R, S] { @@ -60,13 +60,14 @@ class SttpBackendStub[R[_], S] private ( def whenRequestMatchesPartial( partial: PartialFunction[Request[_, _], Response[_]]) : SttpBackendStub[R, S] = { - new SttpBackendStub(rm, matchers.orElse(partial), fallback) + val wrappedPartial = partial.andThen(rm.unit) + new SttpBackendStub(rm, matchers.orElse(wrappedPartial), fallback) } override def send[T](request: Request[T, S]): R[Response[T]] = { Try(matchers.lift(request)) match { - case Success(Some(response)) => - wrapResponse(tryAdjustResponseType(request.response, response)) + case Success(Some(responseMonad)) => + tryAdjustResponseType(rm, request.response, wrapResponse(responseMonad)) case Success(None) => fallback match { case None => @@ -85,6 +86,9 @@ class SttpBackendStub[R[_], S] private ( private def wrapResponse[T](r: Response[_]): R[Response[T]] = rm.unit(r.asInstanceOf[Response[T]]) + private def wrapResponse[T](r: R[Response[_]]): R[Response[T]] = + rm.map(r)(_.asInstanceOf[Response[T]]) + override def close(): Unit = {} override def responseMonad: MonadError[R] = rm @@ -104,7 +108,13 @@ class SttpBackendStub[R[_], S] private ( def thenRespond[T](body: T): SttpBackendStub[R, S] = thenRespond(Response[T](Right(body), 200, "OK", Nil, Nil)) def thenRespond[T](resp: => Response[T]): SttpBackendStub[R, S] = { - val m: PartialFunction[Request[_, _], Response[_]] = { + val m: PartialFunction[Request[_, _], R[Response[_]]] = { + case r if p(r) => rm.unit(resp) + } + new SttpBackendStub(rm, matchers.orElse(m), fallback) + } + def thenRespondWithMonad(resp: => R[Response[_]]): SttpBackendStub[R, S] = { + val m: PartialFunction[Request[_, _], R[Response[_]]] = { case r if p(r) => resp } new SttpBackendStub(rm, matchers.orElse(m), fallback) @@ -159,13 +169,19 @@ object SttpBackendStub { PartialFunction.empty, Some(fallback)) - private[sttp] def tryAdjustResponseType[T, U](ra: ResponseAs[T, _], - r: Response[U]): Response[_] = { - r.body match { - case Left(_) => r - case Right(body) => - val newBody: Any = tryAdjustResponseBody(ra, body).getOrElse(body) - r.copy(body = Right(newBody)) + private[sttp] def tryAdjustResponseType[DesiredRType, RType, M[_]]( + rm: MonadError[M], + ra: ResponseAs[DesiredRType, _], + m: M[Response[RType]]): M[Response[DesiredRType]] = { + rm.map[Response[RType], Response[DesiredRType]](m) { r => + r.body match { + case Left(_) => r.asInstanceOf[Response[DesiredRType]] + case Right(body) => + val newBody: Any = tryAdjustResponseBody(ra, body).getOrElse(body) + r.copy( + body = + Right[String, DesiredRType](newBody.asInstanceOf[DesiredRType])) + } } } diff --git a/core/src/test/scala/com/softwaremill/sttp/testing/SttpBackendStubTests.scala b/core/src/test/scala/com/softwaremill/sttp/testing/SttpBackendStubTests.scala index f3d2cd4..8a4eeb2 100644 --- a/core/src/test/scala/com/softwaremill/sttp/testing/SttpBackendStubTests.scala +++ b/core/src/test/scala/com/softwaremill/sttp/testing/SttpBackendStubTests.scala @@ -8,6 +8,8 @@ import com.softwaremill.sttp._ import org.scalatest.concurrent.ScalaFutures import org.scalatest.{FlatSpec, Matchers} +import scala.concurrent.Future + class SttpBackendStubTests extends FlatSpec with Matchers with ScalaFutures { private val testingStub = SttpBackendStub(HttpURLConnectionBackend()) .whenRequestMatches(_.uri.path.startsWith(List("a", "b"))) @@ -26,6 +28,8 @@ class SttpBackendStubTests extends FlatSpec with Matchers with ScalaFutures { List("partialAda")) => Response(Right("Ada"), 200, "OK", Nil, Nil) }) + .whenRequestMatches(_.uri.port.exists(_ == 8080)) + .thenRespondWithMonad(Response(Right("OK from monad"), 200, "OK", Nil, Nil)) "backend stub" should "use the first rule if it matches" in { implicit val b = testingStub @@ -50,6 +54,14 @@ class SttpBackendStubTests extends FlatSpec with Matchers with ScalaFutures { r.body should be('right) } + it should "respond with monad with set response" in { + implicit val b = testingStub + val r = sttp.post(uri"http://example.org:8080").send() + r.is200 should be(true) + r.body should be('right) + r.body.right.get should be("OK from monad") + } + it should "use the default response if no rule matches" in { implicit val b = testingStub val r = sttp.put(uri"http://example.org/d").send() @@ -155,6 +167,25 @@ class SttpBackendStubTests extends FlatSpec with Matchers with ScalaFutures { } + it should "not hold the calling thread when passed a future monad" in { + val LongTimeMillis = 10000L + val before = System.currentTimeMillis() + + implicit val s = SttpBackendStub(new FutureMonad()).whenAnyRequest + .thenRespondWithMonad(Future { + Thread.sleep(LongTimeMillis) + Response(Right("OK"), 200, "", Nil, Nil) + }) + + sttp + .get(uri"http://example.org") + .send() + + val after = System.currentTimeMillis() + + (after - before) should be < LongTimeMillis + } + private val testingStubWithFallback = SttpBackendStub .withFallback(testingStub) .whenRequestMatches(_.uri.path.startsWith(List("c"))) diff --git a/docs/testing.rst b/docs/testing.rst index bb2e3d5..ead722b 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -49,6 +49,18 @@ It is also possible to match requests by partial function, returning a response. This approach to testing has one caveat: the responses are not type-safe. That is, the stub backend cannot match on or verify that the type of the response body matches the response body type requested. +Another way to specify the behaviour is passing a response monad to Stub. It is useful if you need to test scenario with slow server, when response should be not returned immediately but after some time. +Example with Futures: :: + + implicit val testingBackend = SttpBackendStub(new FutureMonad()).whenAnyRequest + .thenRespondWithMonad(Future { + Thread.sleep(5000) + Response(Right("OK"), 200, "", Nil, Nil) + }) + + val respFuture = sttp.get(uri"http://example.org").send() + // responseFuture will complete after 10 seconds with "OK" response + Simulating exceptions --------------------- |