diff options
author | adamw <adam@warski.org> | 2017-10-09 17:03:44 +0200 |
---|---|---|
committer | adamw <adam@warski.org> | 2017-10-09 17:03:44 +0200 |
commit | 5620043c1fc9fc846fa4a5fe91b2af7fb1ff6008 (patch) | |
tree | 6cc71430df59ad68815352be26696b835e3354ac | |
parent | e093ee494d45350818933049c498ec5eb4fb98be (diff) | |
download | sttp-5620043c1fc9fc846fa4a5fe91b2af7fb1ff6008.tar.gz sttp-5620043c1fc9fc846fa4a5fe91b2af7fb1ff6008.tar.bz2 sttp-5620043c1fc9fc846fa4a5fe91b2af7fb1ff6008.zip |
Backend stubs with fallback (thx to @gabro)
-rw-r--r-- | README.md | 18 | ||||
-rw-r--r-- | core/src/main/scala/com/softwaremill/sttp/testing/SttpBackendStub.scala | 39 | ||||
-rw-r--r-- | core/src/test/scala/com/softwaremill/sttp/testing/SttpBackendStubTests.scala | 21 |
3 files changed, 67 insertions, 11 deletions
@@ -560,6 +560,23 @@ 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. +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: + +```scala +implicit val testingBackend = SttpBackendStub.withFallback(HttpURLConnectionBackend()) + .whenRequestMatches(_.uri.path.startsWith(List("a"))) + .thenRespond("I'm a STUB!") + +val response1 = sttp.get(uri"http://api.internal/a").send() +// response1.body will be Right("I'm a STUB") + +val response2 = sttp.post(uri"http://api.internal/b").send() +// response2 will be whatever a "real" network call to api.internal/b returns +``` + ## Notes * the encoding for `String`s defaults to `utf-8`. @@ -593,3 +610,4 @@ and pick a task you'd like to work on! * [Bjørn Madsen](https://github.com/aeons) * [Piotr Buda](https://github.com/pbuda) * [Piotr Gabara](https://github.com/bhop) +* [Gabriele Petronella](https://github.com/gabro) 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 5724264..aa04ef3 100644 --- a/core/src/main/scala/com/softwaremill/sttp/testing/SttpBackendStub.scala +++ b/core/src/main/scala/com/softwaremill/sttp/testing/SttpBackendStub.scala @@ -19,7 +19,8 @@ import scala.language.higherKinds * a response is specified with the incorrect body type. */ class SttpBackendStub[R[_], S] private (rm: MonadError[R], - matchers: Vector[Matcher[_]]) + matchers: Vector[Matcher[_]], + fallback: Option[SttpBackend[R, S]]) extends SttpBackend[R, S] { /** @@ -31,15 +32,22 @@ class SttpBackendStub[R[_], S] private (rm: MonadError[R], new WhenRequest(p) override def send[T](request: Request[T, S]): R[Response[T]] = { - val response = matchers + matchers .collectFirst { case matcher if matcher(request) => matcher.response - } - .getOrElse(DefaultResponse) - - rm.unit(response.asInstanceOf[Response[T]]) + } match { + case Some(response) => wrapResponse(response) + case None => + fallback match { + case None => wrapResponse(DefaultResponse) + case Some(fb) => fb.send(request) + } + } } + private def wrapResponse[T](r: Response[_]): R[Response[T]] = + rm.unit(r.asInstanceOf[Response[T]]) + override def close(): Unit = {} override def responseMonad: MonadError[R] = rm @@ -57,7 +65,7 @@ class SttpBackendStub[R[_], S] private (rm: MonadError[R], 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] = - new SttpBackendStub(rm, matchers :+ Matcher(p, resp)) + new SttpBackendStub(rm, matchers :+ Matcher(p, resp), fallback) } } @@ -71,16 +79,27 @@ object SttpBackendStub { * [[https://stackoverflow.com/questions/46642623/cannot-infer-contravariant-nothing-type-parameter]]. */ def apply[R[_], S, S2 <: S](c: SttpBackend[R, S]): SttpBackendStub[R, S2] = - new SttpBackendStub[R, S2](c.responseMonad, Vector.empty) + new SttpBackendStub[R, S2](c.responseMonad, Vector.empty, None) /** * Create a stub backend using the given response monad (which determines * how requests are wrapped), and any stream type. */ def apply[R[_], S](responseMonad: MonadError[R]): SttpBackendStub[R, S] = - new SttpBackendStub[R, S](responseMonad, Vector.empty) + new SttpBackendStub[R, S](responseMonad, Vector.empty, None) + + /** + * Create a stub backend which delegates send requests to the given fallback + * backend, if the request doesn't match any of the specified predicates. + */ + def withFallback[R[_], S, S2 <: S]( + fallback: SttpBackend[R, S]): SttpBackendStub[R, S2] = + new SttpBackendStub[R, S2](fallback.responseMonad, + Vector.empty, + Some(fallback)) - private val DefaultResponse = Response[Nothing](Left(""), 404, Nil, Nil) + private val DefaultResponse = + Response[Nothing](Left("Not Found"), 404, Nil, Nil) private case class Matcher[T](p: Request[T, _] => Boolean, response: Response[T]) { 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 538dd35..b80ae76 100644 --- a/core/src/test/scala/com/softwaremill/sttp/testing/SttpBackendStubTests.scala +++ b/core/src/test/scala/com/softwaremill/sttp/testing/SttpBackendStubTests.scala @@ -13,7 +13,7 @@ class SttpBackendStubTests extends FlatSpec with Matchers with ScalaFutures { .whenRequestMatches(_.method == Method.GET) .thenRespondServerError() - it should "use the first rule if it matches" in { + "backend stub" should "use the first rule if it matches" in { implicit val b = testingStub val r = sttp.get(uri"http://example.org/a/b/c").send() r.is200 should be(true) @@ -48,4 +48,23 @@ class SttpBackendStubTests extends FlatSpec with Matchers with ScalaFutures { val r = sttp.post(uri"http://example.org").send() r.futureValue.code should be(404) } + + val testingStubWithFallback = SttpBackendStub + .withFallback(testingStub) + .whenRequestMatches(_.uri.path.startsWith(List("c"))) + .thenRespond("ok") + + "backend stub with fallback" should "use the stub when response for a request is defined" in { + implicit val b = testingStubWithFallback + + val r = sttp.post(uri"http://example.org/c").send() + r.body should be(Right("ok")) + } + + it should "delegate to the fallback for unhandled requests" in { + implicit val b = testingStubWithFallback + + val r = sttp.post(uri"http://example.org/a/b").send() + r.is200 should be(true) + } } |