Skip to content

Commit

Permalink
#43: supporting exceptions in the stub backend
Browse files Browse the repository at this point in the history
  • Loading branch information
adamw committed Nov 13, 2017
1 parent 9635a25 commit 927c7c7
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)

}
}
}
Expand Down Expand Up @@ -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
})
Expand Down
Original file line number Diff line number Diff line change
@@ -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}
Expand Down Expand Up @@ -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")))
Expand Down
12 changes: 12 additions & 0 deletions docs/backends/testing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down

0 comments on commit 927c7c7

Please sign in to comment.