hspec-1.0.0: Test/Hspec/Monadic.hs
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-- | This module contains the runners that take a set of specs, specified in a monadic style, evaluate their examples, and
-- report to a given handle.
--
-- The three functions you'll use the most are 'hspec', 'describe', and 'it'. Here is an
-- example of functions that format and unformat phone numbers and the specs for them.
--
-- > import Test.Hspec.Monadic
-- > import Test.Hspec.QuickCheck
-- > import Test.Hspec.HUnit
-- > import Test.QuickCheck
-- > import Test.HUnit
-- >
-- > main = hspec mySpecs
--
-- Since the specs are often used to tell you what to implement, it's best to start with
-- undefined functions. Once we have some specs, then you can implement each behavior
-- one at a time, ensuring that each behavior is met and there is no undocumented behavior.
--
-- > unformatPhoneNumber :: String -> String
-- > unformatPhoneNumber number = undefined
-- >
-- > formatPhoneNumber :: String -> String
-- > formatPhoneNumber number = undefined
--
-- The 'describe' function takes a list of behaviors and examples bound together with the 'it' function
--
-- > mySpecs = describe "unformatPhoneNumber" $ do
--
-- A boolean expression can act as a behavior's example.
--
-- > it "removes dashes, spaces, and parenthesies" $
-- > unformatPhoneNumber "(555) 555-1234" == "5555551234"
--
-- The 'pending' function marks a behavior as pending an example. The example doesn't count as failing.
--
-- > it "handles non-US phone numbers" $
-- > pending "need to look up how other cultures format phone numbers"
--
-- An HUnit 'Test' can act as a behavior's example. (must import @Test.Hspec.HUnit@)
--
-- > it "removes the \"ext\" prefix of the extension" $ do
-- > let expected = "5555551234135"
-- > actual = unformatPhoneNumber "(555) 555-1234 ext 135"
-- > assertEqual "remove extension" expected actual
--
-- An @IO()@ action is treated like an HUnit 'TestCase'. (must import @Test.Hspec.HUnit@)
--
-- > it "converts letters to numbers" $ do
-- > let expected = "6862377"
-- > actual = unformatPhoneNumber "NUMBERS"
-- > assertEqual "letters to numbers" expected actual
--
-- The 'property' function allows a QuickCheck property to act as an example. (must import @Test.Hspec.HUnit@)
--
-- > it "can add and remove formatting without changing the number" $ property $
-- > forAll phoneNumber $ \ n -> unformatPhoneNumber (formatPhoneNumber n) == n
-- >
-- > phoneNumber :: Gen String
-- > phoneNumber = do
-- > nums <- elements [7,10,11,12,13,14,15]
-- > vectorOf nums (elements "0123456789")
--
module Test.Hspec.Monadic (
-- * Types
Spec
, Result
, Specs
-- * Defining a spec
, describe
, it
, pending
-- * Running a spec
, hspec
, hspecB
, hspecX
, hHspec
-- * Interface to the non-monadic API
, runSpecM
, fromSpecList
-- * Deprecated functions
, descriptions
) where
import System.IO
import Test.Hspec.Core hiding (describe,descriptions,it)
import qualified Test.Hspec.Core as Core
import qualified Test.Hspec.Runner as Runner
import Control.Monad.Trans.Writer (Writer, execWriter, tell)
type Specs = SpecM ()
newtype SpecM a = SpecM (Writer [UnevaluatedSpec] a)
deriving Monad
-- | Create a document of the given specs and write it to stdout.
hspec :: Specs -> IO [EvaluatedSpec]
hspec = Runner.hspec . runSpecM
-- | Use in place of @hspec@ to also exit the program with an @ExitCode@
hspecX :: Specs -> IO a
hspecX = Runner.hspecX . runSpecM
-- | Use in place of hspec to also give a @Bool@ success indication
hspecB :: Specs -> IO Bool
hspecB = Runner.hspecB . runSpecM
-- | Create a document of the given specs and write it to the given handle.
--
-- > writeReport filename specs = withFile filename WriteMode (\ h -> hHspec h specs)
--
hHspec :: Handle -> Specs -> IO [EvaluatedSpec]
hHspec h = Runner.hHspec h . runSpecM
-- | Convert a monadic spec into a non-monadic spec.
runSpecM :: Specs -> [UnevaluatedSpec]
runSpecM (SpecM specs) = execWriter specs
-- | Convert a non-monadic spec into a monadic spec.
fromSpecList :: [UnevaluatedSpec] -> Specs
fromSpecList = SpecM . tell
describe :: String -> Specs -> Specs
describe label action = SpecM . tell $ [Core.describe label (runSpecM action)]
it :: Example v => String -> v -> Specs
it label action = (SpecM . tell) [Core.it label action]
-- | DEPRECATED: Use `sequence_` instead.
descriptions :: [Specs] -> Specs
descriptions = sequence_
{-# DEPRECATED descriptions "use sequence_ instead" #-}