tentative improvements
This commit is contained in:
parent
8dc323ef0a
commit
fbd9f3ec29
1 changed files with 39 additions and 17 deletions
|
@ -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
|
||||
`servant-client` uses, for testing purposes. My response: to ad-hoc extend
|
||||
`servant-client` (as for tests), use `Servant.Client.Free`. This recipe is an
|
||||
evidence.
|
||||
or simply put: _a practical introduction to `Servant.Client.Free`_.
|
||||
By Oleg Grenrus
|
||||
|
||||
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.
|
||||
|
||||
|
@ -48,9 +51,9 @@ api :: Proxy API
|
|||
api = Proxy
|
||||
```
|
||||
|
||||
Next we implement a `main`. If passed `server` it will run `server`, if
|
||||
`client` a small `test` (to be defined next) will be run. This should be pretty
|
||||
straigh-forward:
|
||||
Next we implement a `main`. If passed `server` it will run `server`, if passed
|
||||
`client` it will run a small `test` (to be defined next) will be run. This should be
|
||||
pretty straightforward:
|
||||
|
||||
```haskell
|
||||
main :: IO ()
|
||||
|
@ -70,20 +73,29 @@ main = do
|
|||
|
||||
## Test
|
||||
|
||||
In the actual test, we'll use a `Servant.Client.Free` client.
|
||||
Cecause we have a single endpoint API, we'll get a single client function:
|
||||
In the client part, we will use a `Servant.Client.Free` client.
|
||||
Because we have a single endpoint API, we'll get a single client function,
|
||||
running in the `Free ClientF` (free) monad:
|
||||
|
||||
```haskell
|
||||
fcli :: Int -> Free ClientF Int
|
||||
fcli = client api
|
||||
getSquare :: Int -> Free ClientF Int
|
||||
getSquare = client api
|
||||
```
|
||||
|
||||
Next, we can write our small test. We'll pass a value to `fcli` and inspect
|
||||
the `Free` structure. First three possibilities are self-explanatory:
|
||||
Such clients are "client functions without a backend", so to speak,
|
||||
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
|
||||
test :: IO ()
|
||||
test = case fcli 42 of
|
||||
test = case getSquare 42 of
|
||||
Pure n ->
|
||||
putStrLn $ "ERROR: got pure result: " ++ show n
|
||||
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
|
||||
validate that they or the backend does what we expect (think: debugger). At the
|
||||
end an example of client run (prettified):
|
||||
validate that the client functions or the HTTP client backend does what we expect
|
||||
(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 {
|
||||
|
|
Loading…
Reference in a new issue