aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdam Warski <adam@warski.org>2018-01-29 14:12:23 +0100
committerGitHub <noreply@github.com>2018-01-29 14:12:23 +0100
commit88a3a820c8233d335242e51936bc22723c391cd3 (patch)
tree7471e7705b333f92ad0699621b558ee93c3f610a
parentb5e8aa36e439e96c1b99a83973a1b0fda23b1d15 (diff)
parent2f2c6b66adf8f2157f7c3cdf47d7d784ed105e87 (diff)
downloadsttp-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
-rw-r--r--core/src/main/scala/com/softwaremill/sttp/testing/SttpBackendStub.scala40
-rw-r--r--core/src/test/scala/com/softwaremill/sttp/testing/SttpBackendStubTests.scala31
-rw-r--r--docs/testing.rst12
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
---------------------