tentative improvements

This commit is contained in:
Alp Mestanogullari 2018-07-06 00:36:37 +02:00 committed by GitHub
parent 8dc323ef0a
commit fbd9f3ec29
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -1,9 +1,12 @@
# Using Free Client (for tests) # Inspecting, debugging, simulating clients and more
Someone asked on IRC about getting `Request` & `Response` of what or simply put: _a practical introduction to `Servant.Client.Free`_.
`servant-client` uses, for testing purposes. My response: to ad-hoc extend By Oleg Grenrus
`servant-client` (as for tests), use `Servant.Client.Free`. This recipe is an
evidence. Someone asked on IRC how one could access the intermediate Requests (resp. Responses)
produced (resp. received) by client functions derived using servant-client.
My response to such inquiries is: to extend `servant-client` in an ad-hoc way (e.g for testing or debugging
purposes), use `Servant.Client.Free`. This recipe shows how.
First the module header, but this time We'll comment the imports. First the module header, but this time We'll comment the imports.
@ -48,9 +51,9 @@ api :: Proxy API
api = Proxy api = Proxy
``` ```
Next we implement a `main`. If passed `server` it will run `server`, if Next we implement a `main`. If passed `server` it will run `server`, if passed
`client` a small `test` (to be defined next) will be run. This should be pretty `client` it will run a small `test` (to be defined next) will be run. This should be
straigh-forward: pretty straightforward:
```haskell ```haskell
main :: IO () main :: IO ()
@ -70,20 +73,29 @@ main = do
## Test ## Test
In the actual test, we'll use a `Servant.Client.Free` client. In the client part, we will use a `Servant.Client.Free` client.
Cecause we have a single endpoint API, we'll get a single client function: Because we have a single endpoint API, we'll get a single client function,
running in the `Free ClientF` (free) monad:
```haskell ```haskell
fcli :: Int -> Free ClientF Int getSquare :: Int -> Free ClientF Int
fcli = client api getSquare = client api
``` ```
Next, we can write our small test. We'll pass a value to `fcli` and inspect Such clients are "client functions without a backend", so to speak,
the `Free` structure. First three possibilities are self-explanatory: or where the backend has been abstracted out. To be more precise, `ClientF` is a functor that
precisely represents the operations servant-client-core needs from an http client backend.
So if we are to emulate one or augment what such a backend does, it will be by interpreting
all those operations, the way we want to. This also means we get access to the requests and
responses and can do anything we want with them, right when they are produced or consumed,
respectively.
Next, we can write our small test. We'll pass a value to `getSquare` and inspect
the `Free` structure. The first three possibilities are self-explanatory:
```haskell ```haskell
test :: IO () test :: IO ()
test = case fcli 42 of test = case getSquare 42 of
Pure n -> Pure n ->
putStrLn $ "ERROR: got pure result: " ++ show n putStrLn $ "ERROR: got pure result: " ++ show n
Free (Throw err) -> Free (Throw err) ->
@ -136,8 +148,18 @@ and calling the continuation. We should get a `Pure` value.
``` ```
So that's it. Using `Free` we can evaluate servant clients step-by-step, and So that's it. Using `Free` we can evaluate servant clients step-by-step, and
validate that they or the backend does what we expect (think: debugger). At the validate that the client functions or the HTTP client backend does what we expect
end an example of client run (prettified): (e.g by printing requests/responses on the fly). In fact, using `Servant.Client.Free`
is a little simpler than defining a custom `RunClient` instance, especially
for those cases where it is handy to have the full sequence of client calls
and responses available for us to inspect, since `RunClient` only gives us
access to one `Request` or `Response` at a time.
On the other hand, a "batch collection" of requests and/or responses can be achieved
with both free clients and a custom `RunClient` instance rather easily, for example
by using a `Writer [(Request, Response)]` monad.
Here is an example of running our small `test` against a running server:
``` ```
Making request: Request { Making request: Request {