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