Hspec provides an EDSL to describe tests.
it are used to organize tests with Hspec.
main :: IO () main = hspec $ do describe "Prelude.read" $ do it "can parse integers" $ do read "10" `shouldBe` (10 :: Int) it "can parse floating-point numbers" $ do read "2.5" `shouldBe` (2.5 :: Float)
A spec can consist of multiple
describes can be nested.
main :: IO () main = hspec $ do describe "Prelude" $ do describe "read" $ do it "can parse integers" $ do read "10" `shouldBe` (10 :: Int) describe "head" $ do it "returns the first element of a list" $ do head [23 ..] `shouldBe` 23
context is just an alias for
context with when or
main :: IO () main = hspec $ do describe "parse" $ do context "when provided with invalid input" $ do it "returns a parse error" $ do parse "some invalid input" `shouldBe` Left "parse error"
before_ runs a custom
IO action before every spec item. For example, if you
have an action
flushDb which flushes your database, you can run it before
every spec item with:
main :: IO () main = hspec $ before_ flushDb $ do describe "/api/users/count" $ do it "returns the number of users" $ do post "/api/users/create" "name=Jay" get "/api/users/count" `shouldReturn` 1 context "when there are no users" $ do it "returns 0" $ do get "/api/users/count" `shouldReturn` 0
after_ runs a custom
IO action after every spec item:
main :: IO () main = hspec $ after_ truncateDatabase $ do describe "createUser" $ do it "creates a new user" $ do let eva = User (UserId 3) (Name "Eva") (Age 28) createUser eva getUser (UserId 3) `shouldReturn` eva describe "countUsers" $ do it "counts all registered users" $ do countUsers `shouldReturn` 0
around_ is passed an
IO action for each spec item so that it can perform
whatever setup and teardown is necessary.
serveStubbedApi :: String -> Int -> IO Server stopServer :: Server -> IO () withStubbedApi :: IO () -> IO () withStubbedApi action = bracket (serveStubbedApi "localhost" 80) stopServer (const action) main :: IO () main = hspec $ around_ withStubbedApi $ do describe "api client" $ do it "should authenticate" $ do c <- newClient (Just ("user", "pass")) get c "/api/auth" `shouldReturn` status200 it "should allow anonymous access" $ do c <- newClient Nothing get c "/api/dogs" `shouldReturn` status200
Hooks support passing values to spec items (for example, if you wanted
to open a database connection before each item and pass the connection in).
This can be done with
after. Here's an example
for how to use
openConnection :: IO Connection openConnection = ... closeConnection :: Connection -> IO () closeConnection = ... withDatabaseConnection :: (Connection -> IO ()) -> IO () withDatabaseConnection = bracket openConnection closeConnection spec :: Spec spec = do around withDatabaseConnection $ do describe "createRecipe" $ do it "creates a new recipe" $ \c -> do let ingredients = [Eggs, Butter, Flour, Sugar] createRecipe c (Recipe "Cake" ingredients) getRecipe c "Cake" `shouldReturn` ingredients
pendingWith can be used as a kind of TODO list while you are
writing your specs. If you think of some behaviour that your code should
satisfy, but you are working on something else, you can add a spec item and
just set it to pending.
main :: IO () main = hspec $ do describe "/login" $ do it "should use correct status codes" $ do pending it "should require basic authentication" $ do pendingWith "need to fix base64 first"
It's advisable to resolve all pending spec items before committing your changes.
The report produced at the end of a test run counts passed, failed, and pending spec items separately, and having unresolved pending spec items does not cause your test suite to fail; that is, it can still exit with a status code of 0 if there are unresolved pending spec items.