hspec Testing Framework

Overview

Qualified supports hspec, a Behavior Driven Development (BDD) test framework for Haskell modeled after Ruby's popular Rspec framework.

For more information, see: https://hspec.github.io/

Setup

A minimal test setup for GHC 8.2.2 looks as follows:

Solution

module Example where

add :: Num a => a -> a -> a
add = (+)

Tests

module ExampleSpec where -- test module name MUST end with Spec
import Test.Hspec
import Example

spec :: Spec -- `spec` is required
spec = do
  describe "Example" $ do
    it "add a b" $ do
      (add 1 1) `shouldBe` (2 :: Integer)

main :: IO () -- `main` is optional
main = hspec spec

In Hspec, Expectation is a type alias for IO ()

Pass/Fail methods

shouldBe

shouldBe :: (Eq a, Show a) => a -> a -> Expectation

The vast majority of the time, a test will simply require that a function output a specified value.

Examples:

1 `shouldBe` 1 -- Will pass and display "Test Passed"
Nothing `shouldBe` Just "NOOOO"
{-
expected: Just "NOOOO"
but got: Nothing
-}

shouldSatisfy

shouldSatisfy :: Show a => a -> (a -> Bool) -> Expectation

shouldSatisfy asserts that some predicate holds for a given value.

NOTE: It is not a good considered good practice to write tests with shouldSatisfy, as the output is not generally informative. It is much better to use shouldBe instead.

Examples:

"bar" `shouldSatisfy` (not . null) -- Will pass and display "Test Passed"
 23 `shouldSatisfy` (> 42)  -- Will fail and display:
{-
23 did not satisfy predicate!
-}

shouldReturn

shouldReturn :: (Eq a, Show a) => IO a -> a -> Expectation

shouldReturn asserts that an action returns a given value.

Examples:

putStrLn "Haskell is awesome!" `shouldReturn` ()
-- Will pass and display "Test Passed"

shouldThrow

shouldThrow :: Exception e => IO a -> Selector e -> Expectation

shouldThrow asserts that an exception is thrown. The precise nature of that exception is described with a Selector.

Examples:

error "foobar" `shouldThrow` anyException

A Selector is a predicate, it can simultaneously constrain the type and value of an exception.

throw DivideByZero `shouldThrow` (== DivideByZero)

To select all exceptions of a given type, const True can be used.

error "foobar" `shouldThrow` (const True :: Selector ErrorCall)

For convenience, predefined selectors for some standard exceptions are provided.

error "foobar" `shouldThrow` anyErrorCall

Some exceptions (like ErrorCall) have no Eq instance, so checking for a specific value requires pattern matching.

error "foobar" `shouldThrow` (\e -> case e of
    ErrorCall "foobar" -> True
    _ -> False
    )

For such exceptions, combinators that construct selectors are provided. Each combinator corresponds to a constructor; it takes the same arguments, and has the same name (but starting with a lower-case letter).

error "foobar" `shouldThrow` errorCall "foobar"

hidden

data Hidden = Module String | FromModule String String
hidden :: [Hidden] -> Expectation

Allows the user to hide imported modules and functions from imported modules. Useful for testing functions already implemented in other libraries.

Examples:

Suppose we wanted to write an exercise where the user has to reimplement xor. We don't want them to use xor from Data.Bit, so we don't want the following code to pass.

module Solution where
import qualified Data.Bit (xor)

xor :: Int -> Int
xor = Data.Bit.xor

If we add into the test suite

hidden [Module "Data.Bit"]

Then the above code will fail, since it imports Data.Bit.

If we wanted to allow for the other functions in Data.Bit except xor and and, we could add to our test suite:

hidden [FromModule "Data.Bit" "xor", FromModule "Data.Bit" "and"]

QuickCheck

Using QuickCheck with Hspec

You can use arbitrary QuickCheck properties with Hspec, but they must be of type Property. QuickCheck's property function can be used to turn anything that is a member of the Testable class into a Property.

For more information, see the QuickCheck Tutorial

Example:

module ExampleSpec where -- test module name MUST end with Spec
import Test.Hspec
import Test.QuickCheck

spec :: Spec -- `spec` is required
spec = do
  describe "read" $ do
    context "when used with ints" $ do
      it "is inverse to show" $ property $
        \x -> (read . show) x == (x :: Int)

main :: IO () -- `main` is optional
main = hspec spec