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
|
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 {
|
||||||
|
|
Loading…
Reference in a new issue