From 927c7c73b9766e825e1949667219ee367f342054 Mon Sep 17 00:00:00 2001 From: adamw Date: Mon, 13 Nov 2017 13:40:11 +0100 Subject: #43: supporting exceptions in the stub backend --- .../sttp/testing/SttpBackendStub.scala | 12 +++++------ .../sttp/testing/SttpBackendStubTests.scala | 23 ++++++++++++++++++++++ docs/backends/testing.rst | 12 +++++++++++ 3 files changed, 41 insertions(+), 6 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 f62959f..b894e77 100644 --- a/core/src/main/scala/com/softwaremill/sttp/testing/SttpBackendStub.scala +++ b/core/src/main/scala/com/softwaremill/sttp/testing/SttpBackendStub.scala @@ -4,6 +4,7 @@ import com.softwaremill.sttp.testing.SttpBackendStub._ import com.softwaremill.sttp.{MonadError, Request, Response, SttpBackend} import scala.language.higherKinds +import scala.util.{Failure, Success, Try} /** * A stub backend to use in tests. @@ -42,9 +43,10 @@ class SttpBackendStub[R[_], S] private (rm: MonadError[R], matchers .collectFirst { case matcher: Matcher[T @unchecked] if matcher(request) => - matcher.response(request).get + Try(matcher.response(request).get) } match { - case Some(response) => wrapResponse(response) + case Some(Success(response)) => wrapResponse(response) + case Some(Failure(e)) => rm.error(e) case None => fallback match { case None => wrapResponse(DefaultResponse) @@ -72,10 +74,9 @@ class SttpBackendStub[R[_], S] private (rm: MonadError[R], thenRespond(Response[Nothing](Left(msg), code, Nil, Nil)) def thenRespond[T](body: T): SttpBackendStub[R, S] = thenRespond(Response[T](Right(body), 200, Nil, Nil)) - def thenRespond[T](resp: Response[T]): SttpBackendStub[R, S] = { + def thenRespond[T](resp: => Response[T]): SttpBackendStub[R, S] = { val m = Matcher[T](p, resp) new SttpBackendStub(rm, matchers :+ m, fallback) - } } } @@ -124,8 +125,7 @@ object SttpBackendStub { } private object Matcher { - - def apply[T](p: Request[T, _] => Boolean, response: Response[T]) = { + def apply[T](p: Request[T, _] => Boolean, response: => Response[T]) = { new Matcher[T]({ case r if p(r) => response }) 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 9e10cd7..292d324 100644 --- a/core/src/test/scala/com/softwaremill/sttp/testing/SttpBackendStubTests.scala +++ b/core/src/test/scala/com/softwaremill/sttp/testing/SttpBackendStubTests.scala @@ -1,5 +1,9 @@ package com.softwaremill.sttp.testing +import java.util.concurrent.TimeoutException + +import scala.concurrent.ExecutionContext.Implicits.global + import com.softwaremill.sttp._ import org.scalatest.concurrent.ScalaFutures import org.scalatest.{FlatSpec, Matchers} @@ -70,6 +74,25 @@ class SttpBackendStubTests extends FlatSpec with Matchers with ScalaFutures { ada.body should be(Right("Ada")) } + it should "handle exceptions thrown instead of a response (synchronous)" in { + implicit val s = SttpBackendStub(HttpURLConnectionBackend()) + .whenRequestMatches(_ => true) + .thenRespond(throw new TimeoutException()) + + a[TimeoutException] should be thrownBy { + sttp.get(uri"http://example.org").send() + } + } + + it should "handle exceptions thrown instead of a response (asynchronous)" in { + implicit val s = SttpBackendStub(new FutureMonad()) + .whenRequestMatches(_ => true) + .thenRespond(throw new TimeoutException()) + + val result = sttp.get(uri"http://example.org").send() + result.failed.futureValue shouldBe a[TimeoutException] + } + val testingStubWithFallback = SttpBackendStub .withFallback(testingStub) .whenRequestMatches(_.uri.path.startsWith(List("c"))) diff --git a/docs/backends/testing.rst b/docs/backends/testing.rst index 1618d68..d1e99da 100644 --- a/docs/backends/testing.rst +++ b/docs/backends/testing.rst @@ -35,6 +35,18 @@ It is also possible to match request by partial function, returning a response. However, this approach has one caveat: the responses are not type-safe. That is, the backend cannot match on or verify that the type included in the response matches the response type requested. +Simulating exceptions +--------------------- + +If you want to simulate an exception being thrown by a backend, e.g. a socket timeout exception, you can do so by throwing the appropriate exception instead of the response, e.g.:: + + implicit val testingBackend = SttpBackendStub(HttpURLConnectionBackend()) + .whenRequestMatches(_ => true) + .thenRespond(throw new TimeoutException()) + +Delegating to another backend +----------------------------- + It is also possible to create a stub backend which delegates calls to another (possibly "real") backend if none of the specified predicates match a request. This can be useful during development, to partially stub a yet incomplete API with which we integrate:: implicit val testingBackend = -- cgit v1.2.3