diff options
Diffstat (limited to 'core/src/main/scala/com/softwaremill/sttp/testing/SttpBackendStub.scala')
-rw-r--r-- | core/src/main/scala/com/softwaremill/sttp/testing/SttpBackendStub.scala | 90 |
1 files changed, 90 insertions, 0 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 new file mode 100644 index 0000000..5724264 --- /dev/null +++ b/core/src/main/scala/com/softwaremill/sttp/testing/SttpBackendStub.scala @@ -0,0 +1,90 @@ +package com.softwaremill.sttp.testing + +import com.softwaremill.sttp.testing.SttpBackendStub._ +import com.softwaremill.sttp.{MonadError, Request, Response, SttpBackend} + +import scala.language.higherKinds + +/** + * A stub backend to use in tests. + * + * The stub can be configured to respond with a given response if the + * request matches a predicate (see the [[whenRequestMatches()]] method). + * + * Note however, that this is not type-safe with respect to the type of the + * response body - the stub doesn't have a way to check if the type of the + * body in the configured response is the same as the one specified by the + * request. Hence, the predicates can match requests basing on the URI + * or headers. A [[ClassCastException]] might occur if for a given request, + * a response is specified with the incorrect body type. + */ +class SttpBackendStub[R[_], S] private (rm: MonadError[R], + matchers: Vector[Matcher[_]]) + extends SttpBackend[R, S] { + + /** + * Specify how the stub backend should respond to requests matching the + * given predicate. Note that the stubs are immutable, and each new + * specification that is added yields a new stub instance. + */ + def whenRequestMatches(p: Request[_, _] => Boolean): WhenRequest = + new WhenRequest(p) + + override def send[T](request: Request[T, S]): R[Response[T]] = { + val response = matchers + .collectFirst { + case matcher if matcher(request) => matcher.response + } + .getOrElse(DefaultResponse) + + rm.unit(response.asInstanceOf[Response[T]]) + } + + override def close(): Unit = {} + + override def responseMonad: MonadError[R] = rm + + class WhenRequest(p: Request[_, _] => Boolean) { + def thenRespondOk(): SttpBackendStub[R, S] = + thenRespondWithCode(200) + def thenRespondNotFound(): SttpBackendStub[R, S] = + thenRespondWithCode(404, "Not found") + def thenRespondServerError(): SttpBackendStub[R, S] = + thenRespondWithCode(500, "Internal server error") + def thenRespondWithCode(code: Int, + msg: String = ""): SttpBackendStub[R, S] = + 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] = + new SttpBackendStub(rm, matchers :+ Matcher(p, resp)) + } +} + +object SttpBackendStub { + + /** + * Create a stub backend for testing, which uses the same response wrappers + * and supports the same stream type as the given "real" backend. + * + * @tparam S2 This is a work-around for the problem described here: + * [[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) + + /** + * 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) + + private val DefaultResponse = Response[Nothing](Left(""), 404, Nil, Nil) + + private case class Matcher[T](p: Request[T, _] => Boolean, + response: Response[T]) { + def apply(request: Request[_, _]): Boolean = + p(request.asInstanceOf[Request[T, _]]) + } +} |