Hspec provides an EDSL to describe tests.
describe and 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, and 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 describe. Use context with when or
with, e.g.:
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
Similarly, 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 before, around and after. Here's an example
for how to use around:
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
pending/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.