From e695c37151c84d43e1a37719cc1bbf3fc8366ddb Mon Sep 17 00:00:00 2001 From: adamw Date: Mon, 13 Nov 2017 15:43:11 +0100 Subject: Improving testing docs --- docs/backends/testing.rst | 93 ----------------------------------------------- docs/index.rst | 9 ++++- docs/testing.rst | 93 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 95 deletions(-) delete mode 100644 docs/backends/testing.rst create mode 100644 docs/testing.rst (limited to 'docs') diff --git a/docs/backends/testing.rst b/docs/backends/testing.rst deleted file mode 100644 index d082f40..0000000 --- a/docs/backends/testing.rst +++ /dev/null @@ -1,93 +0,0 @@ -Testing -======= - -If you need a stub backend for use in tests instead of a "real" backend (you probably don't want to make HTTP calls during unit tests), you can use the ``SttpBackendStub`` class. It allows specifying how the backend should respond to requests matching given predicates. - -A backend stub can be created using an instance of a "real" backend, or by explicitly giving the response wrapper monad and supported streams type. - -For example:: - - implicit val testingBackend = SttpBackendStub(HttpURLConnectionBackend()) - .whenRequestMatches(_.uri.path.startsWith(List("a", "b"))) - .thenRespond("Hello there!") - .whenRequestMatches(_.method == Method.POST) - .thenRespondServerError() - - val response1 = sttp.get(uri"http://example.org/a/b/c").send() - // response1.body will be Right("Hello there") - - val response2 = sttp.post(uri"http://example.org/d/e").send() - // response2.code will be 500 - -It is also possible to match request by partial function, returning a response. E.g.:: - - implicit val testingBackend = SttpBackendStub(HttpURLConnectionBackend()) - .whenRequestMatchesPartial({ - case r if r.uri.path.endsWith(List("partial10")) => Response(Right(10), 200, Nil, Nil) - case r if r.uri.path.endsWith(List("partialAda")) => Response(Right("Ada"), 200, Nil, Nil) - }) - - val response1 = sttp.get(uri"http://example.org/partial10").send() - // response1.body will be Right(10) - - val response2 = sttp.post(uri"http://example.org/partialAda").send() - // response2.body will be Right("Ada") - -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()) - -Adjusting the response body type --------------------------------- - -If the type of the response body returned by the stub's rules (as specified using the ``.when...`` methods) doesn't match what was specified in the request, the stub will attempt to convert the body to the desired type. This might be useful when: - -* testing code which maps a basic response body to a custom type, e.g. mapping a raw json string using a decoder to a domain type -* reading a classpath resource (which results in an ``InputStream``) and requesting a response of e.g. type ``String`` - -The following conversions are supported: - -* anything to ``()`` (unit), when the response is ignored -* ``InputStream`` and ``Array[Byte]`` to ``String`` -* ``InputStream`` and ``String`` to ``Array[Byte]`` -* ``InputStream``, ``String`` and ``Array[Byte]`` to custom types through mapped response specifications - -For example:: - - implicit val testingBackend = SttpBackendStub(HttpURLConnectionBackend()) - .whenRequestMatches(_ => true) - .thenRespond(""" {"username": "john", "age": 65 } """) - - def parseUserJson(a: Array[Byte]): User = ... - - val response = sttp.get(uri"http://example.com") - .response(asByteArray.map(parseUserJson)) - .send() - -In the example above, the stub's rules specify that a response with a ``String``-body should be returned for any request; the request, on the other hand, specifies that responses should be parsed from a byte array to a custom ``User`` type. These type don't match, so the ``SttpBackendStub`` will in this case convert the body to the desired type. - -Note that no conversions will be attempted for streaming response bodies. - -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 = - 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 - diff --git a/docs/index.rst b/docs/index.rst index e030323..1e0af24 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -67,8 +67,13 @@ For more details, explore the subjects below! backends/asynchttpclient backends/okhttp backends/custom - backends/testing - + +.. toctree:: + :maxdepth: 2 + :caption: Testing + + testing + .. toctree:: :maxdepth: 2 :caption: Configuration diff --git a/docs/testing.rst b/docs/testing.rst new file mode 100644 index 0000000..7332226 --- /dev/null +++ b/docs/testing.rst @@ -0,0 +1,93 @@ +Testing +======= + +If you need a stub backend for use in tests instead of a "real" backend (you probably don't want to make HTTP calls during unit tests), you can use the ``SttpBackendStub`` class. It allows specifying how the backend should respond to requests matching given predicates. + +A backend stub can be created using an instance of a "real" backend, or by explicitly giving the response wrapper monad and supported streams type. + +For example:: + + implicit val testingBackend = SttpBackendStub(HttpURLConnectionBackend()) + .whenRequestMatches(_.uri.path.startsWith(List("a", "b"))) + .thenRespond("Hello there!") + .whenRequestMatches(_.method == Method.POST) + .thenRespondServerError() + + val response1 = sttp.get(uri"http://example.org/a/b/c").send() + // response1.body will be Right("Hello there") + + val response2 = sttp.post(uri"http://example.org/d/e").send() + // response2.code will be 500 + +It is also possible to match requests by partial function, returning a response. E.g.:: + + implicit val testingBackend = SttpBackendStub(HttpURLConnectionBackend()) + .whenRequestMatchesPartial({ + case r if r.uri.path.endsWith(List("partial10")) => Response(Right(10), 200, Nil, Nil) + case r if r.uri.path.endsWith(List("partialAda")) => Response(Right("Ada"), 200, Nil, Nil) + }) + + val response1 = sttp.get(uri"http://example.org/partial10").send() + // response1.body will be Right(10) + + val response2 = sttp.post(uri"http://example.org/partialAda").send() + // response2.body will be Right("Ada") + +This approach to testing 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()) + +Adjusting the response body type +-------------------------------- + +If the type of the response body returned by the stub's rules (as specified using the ``.whenXxx`` methods) doesn't match what was specified in the request, the stub will attempt to convert the body to the desired type. This might be useful when: + +* testing code which maps a basic response body to a custom type, e.g. mapping a raw json string using a decoder to a domain type +* reading a classpath resource (which results in an ``InputStream``) and requesting a response of e.g. type ``String`` + +The following conversions are supported: + +* anything to ``()`` (unit), when the response is ignored +* ``InputStream`` and ``Array[Byte]`` to ``String`` +* ``InputStream`` and ``String`` to ``Array[Byte]`` +* ``InputStream``, ``String`` and ``Array[Byte]`` to custom types through mapped response specifications + +For example:: + + implicit val testingBackend = SttpBackendStub(HttpURLConnectionBackend()) + .whenRequestMatches(_ => true) + .thenRespond(""" {"username": "john", "age": 65 } """) + + def parseUserJson(a: Array[Byte]): User = ... + + val response = sttp.get(uri"http://example.com") + .response(asByteArray.map(parseUserJson)) + .send() + +In the example above, the stub's rules specify that a response with a ``String``-body should be returned for any request; the request, on the other hand, specifies that responses should be parsed from a byte array to a custom ``User`` type. These type don't match, so the ``SttpBackendStub`` will in this case convert the body to the desired type. + +Note that no conversions will be attempted for streaming response bodies. + +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 = + 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 + -- cgit v1.2.3