@@ -7,7 +7,36 @@ In this recipe we'll work through some common testing strategies and provide
77examples of utlizing these testing strategies in order to test Servant
88applications.
99
10−This recipe uses the following ingredients:
10+## Testing strategies
11+
12+There are many testing strategies you may wish to employ when testing your
13+Servant application, but included below are three common testing patterns:
14+
15+- We'll use `servant-client` to derive client functions and then make valid
16+requests of our API, running in another thread. This is great for testing
17+that our **business logic** is correctly implemented with only valid HTTP
18+requests.
19+
20+- We'll also use `hspec-wai` to make **arbitrary HTTP requests**, in order to
21+test how our application may respond to invalid or otherwise unexpected
22+requests.
23+
24+- Finally, we can also use `servant-quickcheck` for **whole-API tests**, in order
25+to assert that our entire application conforms to **best practices**.
26+
27+## Useful Libraries
28+
29+The following libraries will often come in handy when we decide to test our
30+Servant applications:
31+
32+- [hspec](https://hspec.github.io/)
33+- [hspec-wai](http://hackage.haskell.org/package/hspec-wai)
34+- [QuickCheck](http://hackage.haskell.org/package/QuickCheck)
35+- [servant-quickcheck](https://hackage.haskell.org/package/servant-quickcheck)
36+
37+## Imports and Our Testing Module
38+
39+This recipe starts the following ingredients:
1140
1241```haskell
1342{-# LANGUAGE OverloadedStrings, TypeFamilies, DataKinds,
@@ -80,10 +109,13 @@ type UserApi =
80109
81110A real server would likely use a database to store, retrieve, and validate
82111users, but we're going to do something really simple merely to have something
83−to test. With that said, here's a sample handler for the endpoint described
84−above:
112+to test. With that said, here's a sample handler, server, and `Application`
113+for the endpoint described above:
85114
86115```haskell
116+userApp :: Application
117+userApp = serve (Proxy :: Proxy UserApi) userServer
118+
87119userServer :: Server UserApi
88120userServer = createUser
89121
@@ -107,26 +139,34 @@ of it and see how it responds.
107139Let's write some tests:
108140
109141```haskell
142+withUserApp :: IO () -> IO ()
143+withUserApp action =
144+ -- we can spin up a server in another thread and kill that thread when done
145+ -- in an exception-safe way
146+ bracket (liftIO $ C.forkIO $ Warp.run 8888 userApp)
147+ C.killThread
148+ (const action)
149+
150+
110151businessLogicSpec :: Spec
111−businessLogicSpec = do
112− -- create a test client function
113− let createUser = client (Proxy :: Proxy UserApi)
114− -- create a servant-client ClientEnv
115− baseUrl <- runIO $ parseBaseUrl "http://localhost:8888"
116− manager <- runIO $ newManager defaultManagerSettings
117− let clientEnv = mkClientEnv manager baseUrl
118−
119− -- Run the server in another thread (`runIO` is from `hspec`)
120− runIO $ C.forkIO $ Warp.run 8888 (serve (Proxy :: Proxy UserApi) userServer)
121−
122− -- testing scenarios start here
123− describe "POST /user" $ do
124− it "should create a user with a high enough ID" $ do
125− result <- runClientM (createUser 50001) clientEnv
126− result `shouldBe` (Right $ User { name = "some user", user_id = 50001})
127− it "will it fail with a too-small ID?" $ do
128− result <- runClientM (createUser 4999) clientEnv
129− result `shouldBe` (Right $ User { name = "some user", user_id = 50001})
152+businessLogicSpec =
153+ -- `around` will our Server before the tests and turn it off after
154+ around_ withUserApp $ do
155+ -- create a test client function
156+ let createUser = client (Proxy :: Proxy UserApi)
157+ -- create a servant-client ClientEnv
158+ baseUrl <- runIO $ parseBaseUrl "http://localhost:8888"
159+ manager <- runIO $ newManager defaultManagerSettings
160+ let clientEnv = mkClientEnv manager baseUrl
161+
162+ -- testing scenarios start here
163+ describe "POST /user" $ do
164+ it "should create a user with a high enough ID" $ do
165+ result <- runClientM (createUser 50001) clientEnv
166+ result `shouldBe` (Right $ User { name = "some user", user_id = 50001})
167+ it "will it fail with a too-small ID?" $ do
168+ result <- runClientM (createUser 4999) clientEnv
169+ result `shouldBe` (Right $ User { name = "some user", user_id = 50001})
130170```
131171
132172### Running These Tests
@@ -271,8 +311,8 @@ esTestServer = getESDocument
271311-- here specifically to trigger different behavior in our tests.
272312getESDocument :: Integer -> Handler Value
273313getESDocument docId
274− -- arbitrary things we can use in our tests to simulate failure
275− -- We want to trigger different code paths
314+ -- arbitrary things we can use in our tests to simulate failure:
315+ -- we want to trigger different code paths.
276316 | docId > 1000 = throwError err500
277317 | docId > 500 = pure . Object $ HM.fromList [("bad", String "data")]
278318 | otherwise = pure $ Object $ HM.fromList [("_source", Object $ HM.fromList [("a", String "b")])]
@@ -289,7 +329,7 @@ Hopefully, this will simplify our testing code:
289329```haskell
290330thirdPartyResourcesSpec :: Spec
291331thirdPartyResourcesSpec = around_ withElasticsearch $ do
292− -- we call `with` and pass our own servant-server `Application`
332+ -- we call `with` from `hspec-wai` and pass *real* `Application`
293333 with (pure $ serve (Proxy :: Proxy DocApi) $ docServer "localhost" "9999") $ do
294334 describe "GET /docs" $ do
295335 it "should be able to get a document" $
@@ -450,8 +490,13 @@ another web framework. You have to specify whether you're looking for
450490
451491There are lots of techniques for testing and we only covered a few here.
452492
453−Useful libraries such as `hspec-wai` have ways of testing Wai `Application`s
493+Useful libraries such as `hspec-wai` have ways of running Wai `Application`s
454494and sending requests to them, while Servant's type-level DSL for defining APIs
455−allows us to more easily mock out servers. Lastly, if you want a broad
456−overview of where your application fits in with regard to best practices,
457−consider using `servant-quickcheck`.
495+allows us to more easily mock out servers and to derive clients, which will
496+only craft valid requests.
497+
498+Lastly, if you want a broad overview of where your application fits in with
499+regard to best practices, consider using `servant-quickcheck`.
500+
501+This program is available as a cabal project
502+[here](https://github.com/haskell-servant/servant/tree/master/doc/cookbook/testing).