HMock-0.1.0.1: A flexible mock framework for testing effectful code.
Safe HaskellNone
LanguageHaskell2010

Test.HMock

Description

This module provides a monad transformer, MockT, which can be used to test with mocks of Haskell mtl-style type classes. To use a mock, you define the expected actions and their results, and then run the code you are testing. The framework verifies that the behavior of the code matched your expectations.

For an introduction to the idea of mocks, see Mocks Aren't Stubs, by Martin Fowler.

WARNING: Hmock's API is likely to change soon. Please ensure you use an upper bound on the version number. The current API works fine for mocking with MTL-style classes. I want HMock to also work with effect systems, servant, haxl, and more. To accomplish this, I'll need to make breaking changes to the API.

Suppose you have a MonadFilesystem typeclass, which is instantiated by monads that implement filesystem operations:

class Monad m => MonadFilesystem m where
  readFile :: FilePath -> m String
  writeFile :: FilePath -> String -> m ()

You can use HMock to test code using MonadFilesystem like this:

copyFile :: MonadFilesystem m => FilePath -> FilePath -> m ()
copyFile a b = readFile a >>= writeFile b

makeMockable ''MonadFilesystem

spec = describe "copyFile" $
  it "reads a file and writes its contents to another file" $
    runMockT $ do
      expect $ ReadFile "foo.txt" |-> "contents"
      expect $ WriteFile "bar.txt" "contents" |-> ()
      copyFile "foo.txt" "bar.txt"

The Template Haskell splice, makeMockable, generates the boilerplate needed to use MonadFilesystem with HMock. You then use runMockT to begin a test with mocks, expect to set up your expected actions and responses, and finally execute your code.

Synopsis

Running mocks

data MockT m a Source #

Monad transformer for running mocks.

Instances

Instances details
MonadTrans MockT Source # 
Instance details

Defined in Test.HMock.Internal.MockT

Methods

lift :: Monad m => m a -> MockT m a #

ExpectContext MockT Source # 
Instance details

Defined in Test.HMock.Internal.MockT

Methods

fromExpectSet :: forall (m :: Type -> Type). MonadIO m => ExpectSet (Step m) -> MockT m () Source #

(MonadReader r m, MonadWriter w m, MonadState s m) => MonadRWS r w s (MockT m) Source # 
Instance details

Defined in Test.HMock.Internal.MockT

MonadBase b m => MonadBase b (MockT m) Source # 
Instance details

Defined in Test.HMock.Internal.MockT

Methods

liftBase :: b α -> MockT m α #

MonadWriter w m => MonadWriter w (MockT m) Source # 
Instance details

Defined in Test.HMock.Internal.MockT

Methods

writer :: (a, w) -> MockT m a #

tell :: w -> MockT m () #

listen :: MockT m a -> MockT m (a, w) #

pass :: MockT m (a, w -> w) -> MockT m a #

MonadState s m => MonadState s (MockT m) Source # 
Instance details

Defined in Test.HMock.Internal.MockT

Methods

get :: MockT m s #

put :: s -> MockT m () #

state :: (s -> (a, s)) -> MockT m a #

MonadReader r m => MonadReader r (MockT m) Source # 
Instance details

Defined in Test.HMock.Internal.MockT

Methods

ask :: MockT m r #

local :: (r -> r) -> MockT m a -> MockT m a #

reader :: (r -> a) -> MockT m a #

MonadError e m => MonadError e (MockT m) Source # 
Instance details

Defined in Test.HMock.Internal.MockT

Methods

throwError :: e -> MockT m a #

catchError :: MockT m a -> (e -> MockT m a) -> MockT m a #

Monad m => Monad (MockT m) Source # 
Instance details

Defined in Test.HMock.Internal.MockT

Methods

(>>=) :: MockT m a -> (a -> MockT m b) -> MockT m b #

(>>) :: MockT m a -> MockT m b -> MockT m b #

return :: a -> MockT m a #

Functor m => Functor (MockT m) Source # 
Instance details

Defined in Test.HMock.Internal.MockT

Methods

fmap :: (a -> b) -> MockT m a -> MockT m b #

(<$) :: a -> MockT m b -> MockT m a #

MonadFail m => MonadFail (MockT m) Source # 
Instance details

Defined in Test.HMock.Internal.MockT

Methods

fail :: String -> MockT m a #

Applicative m => Applicative (MockT m) Source # 
Instance details

Defined in Test.HMock.Internal.MockT

Methods

pure :: a -> MockT m a #

(<*>) :: MockT m (a -> b) -> MockT m a -> MockT m b #

liftA2 :: (a -> b -> c) -> MockT m a -> MockT m b -> MockT m c #

(*>) :: MockT m a -> MockT m b -> MockT m b #

(<*) :: MockT m a -> MockT m b -> MockT m a #

MonadIO m => MonadIO (MockT m) Source # 
Instance details

Defined in Test.HMock.Internal.MockT

Methods

liftIO :: IO a -> MockT m a #

MonadThrow m => MonadThrow (MockT m) Source # 
Instance details

Defined in Test.HMock.Internal.MockT

Methods

throwM :: Exception e => e -> MockT m a #

MonadCatch m => MonadCatch (MockT m) Source # 
Instance details

Defined in Test.HMock.Internal.MockT

Methods

catch :: Exception e => MockT m a -> (e -> MockT m a) -> MockT m a #

MonadMask m => MonadMask (MockT m) Source # 
Instance details

Defined in Test.HMock.Internal.MockT

Methods

mask :: ((forall a. MockT m a -> MockT m a) -> MockT m b) -> MockT m b #

uninterruptibleMask :: ((forall a. MockT m a -> MockT m a) -> MockT m b) -> MockT m b #

generalBracket :: MockT m a -> (a -> ExitCase b -> MockT m c) -> (a -> MockT m b) -> MockT m (b, c) #

MonadCont m => MonadCont (MockT m) Source # 
Instance details

Defined in Test.HMock.Internal.MockT

Methods

callCC :: ((a -> MockT m b) -> MockT m a) -> MockT m a #

MonadUnliftIO m => MonadUnliftIO (MockT m) Source # 
Instance details

Defined in Test.HMock.Internal.MockT

Methods

withRunInIO :: ((forall a. MockT m a -> IO a) -> IO b) -> MockT m b #

runMockT :: forall m a. MonadIO m => MockT m a -> m a Source #

Runs a test in the MockT monad, handling all of the mocks.

withMockT :: forall m b. MonadIO m => ((forall a. MockT m a -> m a) -> MockT m b) -> m b Source #

Runs a test in the MockT monad. The test can unlift other MockT pieces to the base monad while still acting on the same set of expectations. This can be useful for testing concurrency or similar mechanisms.

test = withMockT $ inMockT -> do
   expect $ ...

   liftIO $ forkIO $ inMockT firstThread
   liftIO $ forkIO $ inMockT secondThread

This is a low-level primitive. Consider using the unliftio package for higher level implementations of multithreading and other primitives.

describeExpectations :: MonadIO m => MockT m String Source #

Fetches a String that describes the current set of outstanding expectations. This is sometimes useful for debugging test code. The exact format is not specified.

verifyExpectations :: MonadIO m => MockT m () Source #

Verifies that all mock expectations are satisfied. You normally don't need to do this, because it happens automatically at the end of your test in runMockT. However, it's occasionally useful to check expectations in the middle of a test, such as before going on to the next stage.

Use of verifyExpectations might signify that you are doing too much in a single test. Consider splitting large tests into a separate test for each case.

byDefault :: forall cls name m r. (MonadIO m, MockableMethod cls name m r) => Rule cls name m r -> MockT m () Source #

Changes the default response for matching actions.

Without byDefault, actions with no explicit response will return the Default value for the type, or undefined if the return type isn't an instance of Default. byDefault replaces that with a new default response, also overriding any previous defaults. The rule passed in must have exactly one response.

Setting expectations

type MockableMethod (cls :: (Type -> Type) -> Constraint) (name :: Symbol) (m :: Type -> Type) (r :: Type) = (Mockable cls, Typeable m, KnownSymbol name, Typeable r) Source #

All constraints needed to mock a method with the given class, name, base monad, and return type.

class Expectable cls name m r e | e -> cls name m r where Source #

Something that can be expected. This type class covers a number of cases:

  • Expecting an exact Action.
  • Expecting anything that matches a Matcher.
  • Adding a return value (with |->) or response (with |=>).

Methods

toRule :: e -> Rule cls name m r Source #

Instances

Instances details
Expectable cls name m r (Matcher cls name m r) Source # 
Instance details

Defined in Test.HMock.Internal.Expectable

Methods

toRule :: Matcher cls name m r -> Rule cls name m r Source #

Expectable cls name m r (Rule cls name m r) Source # 
Instance details

Defined in Test.HMock.Internal.Expectable

Methods

toRule :: Rule cls name m r -> Rule cls name m r Source #

data Rule (cls :: (Type -> Type) -> Constraint) (name :: Symbol) (m :: Type -> Type) (r :: Type) Source #

A rule for matching a method and responding to it when it matches.

The method may be matched by providing either an Action to match exactly, or a Matcher. Exact matching is only available when all method arguments

A Rule may have zero or more responses, which are attached using |-> and |=>. If there are no responses for a Rule, then there must be a default response for that action, and it is used. If more than one response is added, the rule will perform the responses in order, repeating the last response if there are additional matches.

Example:

expect $
  GetLine_ anything
    |-> "hello"
    |=> (GetLine prompt) -> "The prompt was " ++ prompt
    |-> "quit"

Instances

Instances details
Expectable cls name m r (Rule cls name m r) Source # 
Instance details

Defined in Test.HMock.Internal.Expectable

Methods

toRule :: Rule cls name m r -> Rule cls name m r Source #

(|=>) :: Expectable cls name m r expectable => expectable -> (Action cls name m r -> MockT m r) -> Rule cls name m r infixl 1 Source #

Attaches a response to an expectation. This is a very flexible response, which can look at arguments, do things in the base monad, set up more expectations, etc. The matching Action is passed to the response, and is guaranteed to be a match so it's fine to just pattern match on the correct method.

(|->) :: (Monad m, Expectable cls name m r expectable) => expectable -> r -> Rule cls name m r infixl 1 Source #

Attaches a return value to an expectation. This is more convenient than |=> in the common case where you just want to return a known result. e |-> r means the same thing as e |=> const (return r).

class ExpectContext (t :: (Type -> Type) -> Type -> Type) Source #

Type class for contexts in which it makes sense to express an expectation. Notably, this includes MockT, which expects actions to be performed during a test.

Minimal complete definition

fromExpectSet

Instances

Instances details
ExpectContext MockT Source # 
Instance details

Defined in Test.HMock.Internal.MockT

Methods

fromExpectSet :: forall (m :: Type -> Type). MonadIO m => ExpectSet (Step m) -> MockT m () Source #

ExpectContext Expected Source # 
Instance details

Defined in Test.HMock.Internal.Expectable

Methods

fromExpectSet :: forall (m :: Type -> Type). MonadIO m => ExpectSet (Step m) -> Expected m () Source #

expect :: (HasCallStack, MonadIO m, MockableMethod cls name m r, Expectable cls name m r expectable, ExpectContext ctx) => expectable -> ctx m () Source #

Creates an expectation that an action is performed once per given response (or exactly once if there is no response).

runMockT $ do
  expect $
    ReadFile "foo.txt"
      |-> "lorem ipsum"
      |-> "oops, the file changed out from under me!"
  callCodeUnderTest

In this example, readFile must be called exactly twice by the tested code, and will return "lorem ipsum" the first time, but something different the second time.

expectN Source #

Arguments

:: (HasCallStack, MonadIO m, MockableMethod cls name m r, Expectable cls name m r expectable, ExpectContext ctx) 
=> Multiplicity

The number of times the action should be performed.

-> expectable

The action and its response.

-> ctx m () 

Creates an expectation that an action is performed some number of times.

  runMockT $ do
    expect $ MakeList
    expectN (atLeast 2) $
      CheckList "Cindy Lou Who" |-> "nice"

    callCodeUnderTest

expectAny :: (HasCallStack, MonadIO m, MockableMethod cls name m r, Expectable cls name m r expectable, ExpectContext ctx) => expectable -> ctx m () Source #

Specifies a response if a matching action is performed, but doesn't expect anything. This is equivalent to expectN anyMultiplicity, but shorter.

In this example, the later use of whenever overrides earlier uses, but only for calls that match its conditions.

  runMockT $ do
    whenever $ ReadFile_ anything |-> "tlhIngan maH!"
    whenever $ ReadFile "config.txt" |-> "lang: klingon"

    callCodeUnderTest

inSequence :: (MonadIO m, ExpectContext ctx) => (forall ctx'. ExpectContext ctx' => [ctx' m ()]) -> ctx m () Source #

Creates a sequential expectation. Other actions can still happen during the sequence, but these specific expectations must be met in this order.

  inSequence
    [ expect $ MoveForward,
      expect $ TurnRight,
      expect $ MoveForward
    ]

Beware of using inSequence too often. It is appropriate when the property you are testing is that the order of effects is correct. If that's not the purpose of the test, consider adding several independent expectations, instead. This avoids over-asserting, and keeps your tests less brittle.

inAnyOrder :: (MonadIO m, ExpectContext ctx) => (forall ctx'. ExpectContext ctx' => [ctx' m ()]) -> ctx m () Source #

Combines multiple expectations, which can occur in any order. Most of the time, you can achieve the same thing by expecting each separately, but this can be combined in complex expectations to describe more complex ordering constraints.

  inSequence
    [ inAnyOrder
        [ expect $ AdjustMirrors,
          expect $ FastenSeatBelt
        ],
      expect $ StartCar
    ]

anyOf :: (MonadIO m, ExpectContext ctx) => (forall ctx'. ExpectContext ctx' => [ctx' m ()]) -> ctx m () Source #

Combines multiple expectations, requiring exactly one of them to occur.

  anyOf
    [ expect $ ApplyForJob,
      expect $ ApplyForUniversity
    ]

times :: (MonadIO m, ExpectContext ctx) => Multiplicity -> (forall ctx'. ExpectContext ctx' => ctx' m ()) -> ctx m () Source #

Creates a parent expectation that the child expectation will happen a certain number of times. Unlike expectN, the child expectation can be arbitrarily complex and span multiple actions. Also unlike expectN, each new execution will restart response sequences for rules with more than one response.

Different occurrences of the child can be interleaved. In case of ambiguity, progressing on an existing occurrence is preferred over starting a new occurrence.

consecutiveTimes :: (MonadIO m, ExpectContext ctx) => Multiplicity -> (forall ctx'. ExpectContext ctx' => ctx' m ()) -> ctx m () Source #

Creates a parent expectation that the child expectation will happen a certain number of times. Unlike expectN, the child expectation can be arbitrarily complex and span multiple actions. Also unlike expectN, each new execution will restart response sequences for rules with more than one response.

Different occurrences of the child must happen consecutively, with one finishing before the next begins.

Predicates

data Predicate a Source #

A predicate, which tests values and either accepts or rejects them. This is similar to a -> Bool, but also has a Show instance to describe what it is checking.

Predicates are used to define which arguments a general matcher should accept.

Constructors

Predicate 

Fields

Instances

Instances details
Show (Predicate a) Source # 
Instance details

Defined in Test.HMock.Internal.Predicates

anything :: Predicate a Source #

A Predicate that accepts anything at all.

>>> accept anything "foo"
True
>>> accept anything undefined
True

eq :: (Show a, Eq a) => a -> Predicate a Source #

A Predicate that accepts only the given value.

>>> accept (eq "foo") "foo"
True
>>> accept (eq "foo") "bar"
False

neq :: (Show a, Eq a) => a -> Predicate a Source #

A Predicate that accepts anything but the given value.

>>> accept (neq "foo") "foo"
False
>>> accept (neq "foo") "bar"
True

gt :: (Show a, Ord a) => a -> Predicate a Source #

A Predicate that accepts anything greater than the given value.

>>> accept (gt 5) 4
False
>>> accept (gt 5) 5
False
>>> accept (gt 5) 6
True

geq :: (Show a, Ord a) => a -> Predicate a Source #

A Predicate that accepts anything greater than or equal to the given value.

>>> accept (geq 5) 4
False
>>> accept (geq 5) 5
True
>>> accept (geq 5) 6
True

lt :: (Show a, Ord a) => a -> Predicate a Source #

A Predicate that accepts anything less than the given value.

>>> accept (lt 5) 4
True
>>> accept (lt 5) 5
False
>>> accept (lt 5) 6
False

leq :: (Show a, Ord a) => a -> Predicate a Source #

A Predicate that accepts anything less than or equal to the given value.

>>> accept (leq 5) 4
True
>>> accept (leq 5) 5
True
>>> accept (leq 5) 6
False

just :: Predicate a -> Predicate (Maybe a) Source #

A Predicate that accepts Maybe values of Just x, where x matches the given child Predicate.

>>> accept (just (eq "value")) Nothing
False
>>> accept (just (eq "value")) (Just "value")
True
>>> accept (just (eq "value")) (Just "wrong value")
False

left :: Predicate a -> Predicate (Either a b) Source #

A Predicate that accepts an Either value of Left x, where x matches the given child Predicate.

>>> accept (left (eq "value")) (Left "value")
True
>>> accept (left (eq "value")) (Right "value")
False
>>> accept (left (eq "value")) (Left "wrong value")
False

right :: Predicate b -> Predicate (Either a b) Source #

A Predicate that accepts an Either value of Right x, where x matches the given child Predicate.

>>> accept (right (eq "value")) (Right "value")
True
>>> accept (right (eq "value")) (Right "wrong value")
False
>>> accept (right (eq "value")) (Left "value")
False

zipP :: Predicate a -> Predicate b -> Predicate (a, b) Source #

A Predicate that accepts pairs whose elements satisfy the corresponding child Predicates.

>>> accept (zipP (eq "foo") (eq "bar")) ("foo", "bar")
True
>>> accept (zipP (eq "foo") (eq "bar")) ("bar", "foo")
False

zip3P :: Predicate a -> Predicate b -> Predicate c -> Predicate (a, b, c) Source #

A Predicate that accepts 3-tuples whose elements satisfy the corresponding child Predicates.

>>> accept (zip3P (eq "foo") (eq "bar") (eq "qux")) ("foo", "bar", "qux")
True
>>> accept (zip3P (eq "foo") (eq "bar") (eq "qux")) ("qux", "bar", "foo")
False

zip4P :: Predicate a -> Predicate b -> Predicate c -> Predicate d -> Predicate (a, b, c, d) Source #

A Predicate that accepts 3-tuples whose elements satisfy the corresponding child Predicates.

>>> accept (zip4P (eq 1) (eq 2) (eq 3) (eq 4)) (1, 2, 3, 4)
True
>>> accept (zip4P (eq 1) (eq 2) (eq 3) (eq 4)) (4, 3, 2, 1)
False

zip5P :: Predicate a -> Predicate b -> Predicate c -> Predicate d -> Predicate e -> Predicate (a, b, c, d, e) Source #

A Predicate that accepts 3-tuples whose elements satisfy the corresponding child Predicates.

>>> accept (zip5P (eq 1) (eq 2) (eq 3) (eq 4) (eq 5)) (1, 2, 3, 4, 5)
True
>>> accept (zip5P (eq 1) (eq 2) (eq 3) (eq 4) (eq 5)) (5, 4, 3, 2, 1)
False

andP :: Predicate a -> Predicate a -> Predicate a Source #

A Predicate that accepts anything accepted by both of its children.

>>> accept (lt "foo" `andP` gt "bar") "eta"
True
>>> accept (lt "foo" `andP` gt "bar") "quz"
False
>>> accept (lt "foo" `andP` gt "bar") "alpha"
False

orP :: Predicate a -> Predicate a -> Predicate a Source #

A Predicate that accepts anything accepted by either of its children.

>>> accept (lt "bar" `orP` gt "foo") "eta"
False
>>> accept (lt "bar" `orP` gt "foo") "quz"
True
>>> accept (lt "bar" `orP` gt "foo") "alpha"
True

notP :: Predicate a -> Predicate a Source #

A Predicate that inverts another Predicate, accepting whatever its child rejects, and rejecting whatever its child accepts.

>>> accept (notP (eq "negative")) "positive"
True
>>> accept (notP (eq "negative")) "negative"
False

startsWith :: (Show t, IsSequence t, Eq (Element t)) => t -> Predicate t Source #

A Predicate that accepts sequences that start with the given prefix.

>>> accept (startsWith "fun") "fungible"
True
>>> accept (startsWith "gib") "fungible"
False

endsWith :: (Show t, IsSequence t, Eq (Element t)) => t -> Predicate t Source #

A Predicate that accepts sequences that end with the given suffix.

>>> accept (endsWith "ow") "crossbow"
True
>>> accept (endsWith "ow") "trebuchet"
False

hasSubstr :: (Show t, IsSequence t, Eq (Element t)) => t -> Predicate t Source #

A Predicate that accepts sequences that contain the given (consecutive) substring.

>>> accept (hasSubstr "i") "team"
False
>>> accept (hasSubstr "i") "partnership"
True

hasSubsequence :: (Show t, IsSequence t, Eq (Element t)) => t -> Predicate t Source #

A Predicate that accepts sequences that contain the given (not necessarily consecutive) subsequence.

>>> accept (hasSubsequence [1..5]) [1, 2, 3, 4, 5]
True
>>> accept (hasSubsequence [1..5]) [0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0]
True
>>> accept (hasSubsequence [1..5]) [2, 3, 5, 7, 11]
False

caseInsensitive :: (MonoFunctor t, MonoFunctor a, Element t ~ Char, Element a ~ Char) => (t -> Predicate a) -> t -> Predicate a Source #

Transforms a Predicate on Strings or string-like types to match without regard to case.

>>> accept (caseInsensitive startsWith "foo") "FOOTBALL!"
True
>>> accept (caseInsensitive endsWith "ball") "soccer"
False
>>> accept (caseInsensitive eq "time") "TIME"
True
>>> accept (caseInsensitive gt "NOTHING") "everything"
False

matchesRegex :: (RegexLike Regex a, Eq a) => String -> Predicate a Source #

A Predicate that accepts Strings or string-like values matching a regular expression. The expression must match the entire argument.

You should not use caseInsensitive matchesRegex, because regular expression syntax itself is still case-sensitive even when the text you are matching is not. Instead, use matchesCaseInsensitiveRegex.

>>> accept (matchesRegex "x{2,5}y?") "xxxy"
True
>>> accept (matchesRegex "x{2,5}y?") "xyy"
False
>>> accept (matchesRegex "x{2,5}y?") "wxxxyz"
False

matchesCaseInsensitiveRegex :: (RegexLike Regex a, Eq a) => String -> Predicate a Source #

A Predicate that accepts Strings or string-like values matching a regular expression in a case-insensitive way. The expression must match the entire argument.

You should use this instead of caseInsensitive matchesRegex, because regular expression syntax itself is still case-sensitive even when the text you are matching is not.

>>> accept (matchesCaseInsensitiveRegex "x{2,5}y?") "XXXY"
True
>>> accept (matchesCaseInsensitiveRegex "x{2,5}y?") "XYY"
False
>>> accept (matchesCaseInsensitiveRegex "x{2,5}y?") "WXXXYZ"
False

containsRegex :: (RegexLike Regex a, Eq a) => String -> Predicate a Source #

A Predicate that accepts Strings or string-like values containing a match for a regular expression. The expression need not match the entire argument.

You should not use caseInsensitive containsRegex, because regular expression syntax itself is still case-sensitive even when the text you are matching is not. Instead, use containsCaseInsensitiveRegex.

>>> accept (containsRegex "x{2,5}y?") "xxxy"
True
>>> accept (containsRegex "x{2,5}y?") "xyy"
False
>>> accept (containsRegex "x{2,5}y?") "wxxxyz"
True

containsCaseInsensitiveRegex :: (RegexLike Regex a, Eq a) => String -> Predicate a Source #

A Predicate that accepts Strings or string-like values containing a match for a regular expression in a case-insensitive way. The expression need match the entire argument.

You should use this instead of caseInsensitive containsRegex, because regular expression syntax itself is still case-sensitive even when the text you are matching is not.

>>> accept (containsCaseInsensitiveRegex "x{2,5}y?") "XXXY"
True
>>> accept (containsCaseInsensitiveRegex "x{2,5}y?") "XYY"
False
>>> accept (containsCaseInsensitiveRegex "x{2,5}y?") "WXXXYZ"
True

isEmpty :: MonoFoldable t => Predicate t Source #

A Predicate that accepts empty data structures.

>>> accept isEmpty []
True
>>> accept isEmpty [1, 2, 3]
False
>>> accept isEmpty ""
True
>>> accept isEmpty "gas tank"
False

nonEmpty :: MonoFoldable t => Predicate t Source #

A Predicate that accepts non-empty data structures.

>>> accept nonEmpty []
False
>>> accept nonEmpty [1, 2, 3]
True
>>> accept nonEmpty ""
False
>>> accept nonEmpty "gas tank"
True

sizeIs :: MonoFoldable t => Predicate Int -> Predicate t Source #

A Predicate that accepts data structures whose number of elements match the child Predicate.

>>> accept (sizeIs (lt 3)) ['a' .. 'f']
False
>>> accept (sizeIs (lt 3)) ['a' .. 'b']
True

elemsAre :: MonoFoldable t => [Predicate (Element t)] -> Predicate t Source #

A Predicate that accepts data structures whose contents each match the corresponding Predicate in the given list, in the same order.

>>> accept (elemsAre [lt 3, lt 4, lt 5]) [2, 3, 4]
True
>>> accept (elemsAre [lt 3, lt 4, lt 5]) [2, 3, 4, 5]
False
>>> accept (elemsAre [lt 3, lt 4, lt 5]) [2, 10, 4]
False

unorderedElemsAre :: MonoFoldable t => [Predicate (Element t)] -> Predicate t Source #

A Predicate that accepts data structures whose contents each match the corresponding Predicate in the given list, in any order.

>>> accept (unorderedElemsAre [eq 1, eq 2, eq 3]) [1, 2, 3]
True
>>> accept (unorderedElemsAre [eq 1, eq 2, eq 3]) [2, 3, 1]
True
>>> accept (unorderedElemsAre [eq 1, eq 2, eq 3]) [1, 2, 3, 4]
False
>>> accept (unorderedElemsAre [eq 1, eq 2, eq 3]) [1, 3]
False

each :: MonoFoldable t => Predicate (Element t) -> Predicate t Source #

A Predicate that accepts data structures whose elements each match the child Predicate.

>>> accept (each (gt 5)) [4, 5, 6]
False
>>> accept (each (gt 5)) [6, 7, 8]
True
>>> accept (each (gt 5)) []
True

contains :: MonoFoldable t => Predicate (Element t) -> Predicate t Source #

A Predicate that accepts data structures which contain at least one element matching the child Predicate.

>>> accept (contains (gt 5)) [3, 4, 5]
False
>>> accept (contains (gt 5)) [4, 5, 6]
True
>>> accept (contains (gt 5)) []
False

containsAll :: MonoFoldable t => [Predicate (Element t)] -> Predicate t Source #

A Predicate that accepts data structures which contain an element satisfying each of the child predicates. containsAll [p1, p2, ..., pn] is equivalent to contains p1 `andP` contains p2 `andP` ... `andP` contains pn.

>>> accept (containsAll [eq "foo", eq "bar"]) ["bar", "foo"]
True
>>> accept (containsAll [eq "foo", eq "bar"]) ["foo"]
False
>>> accept (containsAll [eq "foo", eq "bar"]) ["foo", "bar", "qux"]
True

containsOnly :: MonoFoldable t => [Predicate (Element t)] -> Predicate t Source #

A Predicate that accepts data structures whose elements all satisfy at least one of the child predicates. containsOnly [p1, p2, ..., pn] is equivalent to each (p1 `orP` p2 `orP` ... `orP` pn).

>>> accept (containsOnly [eq "foo", eq "bar"]) ["foo", "foo"]
True
>>> accept (containsOnly [eq "foo", eq "bar"]) ["foo", "bar"]
True
>>> accept (containsOnly [eq "foo", eq "bar"]) ["foo", "qux"]
False

containsKey :: (IsList t, Item t ~ (k, v)) => Predicate k -> Predicate t Source #

A Predicate that accepts map-like structures which contain a key matching the child Predicate.

>>> accept (containsKey (eq "foo")) [("foo", 1), ("bar", 2)]
True
>>> accept (containsKey (eq "foo")) [("bar", 1), ("qux", 2)]
False

containsEntry :: (IsList t, Item t ~ (k, v)) => Predicate k -> Predicate v -> Predicate t Source #

A Predicate that accepts map-like structures which contain a key/value pair matched by the given child Predicates (one for the key, and one for the value).

>>> accept (containsEntry (eq "foo") (gt 10)) [("foo", 12), ("bar", 5)]
True
>>> accept (containsEntry (eq "foo") (gt 10)) [("foo", 5), ("bar", 12)]
False
>>> accept (containsEntry (eq "foo") (gt 10)) [("bar", 12)]
False

keysAre :: (IsList t, Item t ~ (k, v)) => [Predicate k] -> Predicate t Source #

A Predicate that accepts map-like structures whose keys are exactly those matched by the given list of Predicates, in any order.

>>> accept (keysAre [eq "a", eq "b", eq "c"]) [("a", 1), ("b", 2), ("c", 3)]
True
>>> accept (keysAre [eq "a", eq "b", eq "c"]) [("c", 1), ("b", 2), ("a", 3)]
True
>>> accept (keysAre [eq "a", eq "b", eq "c"]) [("a", 1), ("c", 3)]
False
>>> accept (keysAre [eq "a", eq "b"]) [("a", 1), ("b", 2), ("c", 3)]
False

entriesAre :: (IsList t, Item t ~ (k, v)) => [(Predicate k, Predicate v)] -> Predicate t Source #

A Predicate that accepts map-like structures whose entries are exactly those matched by the given list of Predicate pairs, in any order.

>>> accept (entriesAre [(eq 1, eq 2), (eq 3, eq 4)]) [(1, 2), (3, 4)]
True
>>> accept (entriesAre [(eq 1, eq 2), (eq 3, eq 4)]) [(3, 4), (1, 2)]
True
>>> accept (entriesAre [(eq 1, eq 2), (eq 3, eq 4)]) [(1, 4), (3, 2)]
False
>>> accept (entriesAre [(eq 1, eq 2), (eq 3, eq 4)]) [(1, 2), (3, 4), (5, 6)]
False

approxEq :: (RealFloat a, Show a) => a -> Predicate a Source #

A Predicate that accepts values of RealFloat types that are close to the given number. The expected precision is scaled based on the target value, so that reasonable rounding error is accepted but grossly inaccurate results are not.

The following naive use of eq fails due to rounding:

>>> accept (eq 1.0) (sum (replicate 100 0.01))
False

The solution is to use approxEq, which accounts for rounding error. However, approxEq doesn't accept results that are far enough off that they likely arise from incorrect calculations instead of rounding error.

>>> accept (approxEq 1.0) (sum (replicate 100 0.01))
True
>>> accept (approxEq 1.0) (sum (replicate 100 0.009999))
False

finite :: RealFloat a => Predicate a Source #

A Predicate that accepts finite numbers of any RealFloat type.

>>> accept finite 1.0
True
>>> accept finite (0 / 0)
False
>>> accept finite (1 / 0)
False

infinite :: RealFloat a => Predicate a Source #

A Predicate that accepts infinite numbers of any RealFloat type.

>>> accept infinite 1.0
False
>>> accept infinite (0 / 0)
False
>>> accept infinite (1 / 0)
True

nAn :: RealFloat a => Predicate a Source #

A Predicate that accepts NaN values of any RealFloat type.

>>> accept nAn 1.0
False
>>> accept nAn (0 / 0)
True
>>> accept nAn (1 / 0)
False

is :: HasCallStack => (a -> Bool) -> Predicate a Source #

A conversion from a -> Bool to Predicate. This is a fallback that can be used to build a Predicate that checks anything at all. However, its description will be less helpful than standard Predicates.

>>> accept (is even) 3
False
>>> accept (is even) 4
True

qIs :: HasCallStack => ExpQ -> ExpQ Source #

A Template Haskell splice that acts like is, but receives a quoted expression at compile time and has a more helpful description for error messages.

>>> accept $(qIs [| even |]) 3
False
>>> accept $(qIs [| even |]) 4
True
>>> show $(qIs [| even |])
"even"

with :: HasCallStack => (a -> b) -> Predicate b -> Predicate a Source #

A combinator to lift a Predicate to work on a property or computed value of the original value.

>>> accept (with abs (gt 5)) (-6)
True
>>> accept (with abs (gt 5)) (-5)
False
>>> accept (with reverse (eq "olleh")) "hello"
True
>>> accept (with reverse (eq "olleh")) "goodbye"
False

qWith :: ExpQ -> ExpQ Source #

A Template Haskell splice that acts like is, but receives a quoted typed expression at compile time and has a more helpful description for error messages.

>>> accept ($(qWith [| abs |]) (gt 5)) (-6)
True
>>> accept ($(qWith [| abs |]) (gt 5)) (-5)
False
>>> accept ($(qWith [| reverse |]) (eq "olleh")) "hello"
True
>>> accept ($(qWith [| reverse |]) (eq "olleh")) "goodbye"
False
>>> show ($(qWith [| abs |]) (gt 5))
"abs: > 5"

qMatch :: PatQ -> ExpQ Source #

A Template Haskell splice that turns a quoted pattern into a predicate that accepts values that match the pattern.

>>> accept $(qMatch [p| Just (Left _) |]) Nothing
False
>>> accept $(qMatch [p| Just (Left _) |]) (Just (Left 5))
True
>>> accept $(qMatch [p| Just (Left _) |]) (Just (Right 5))
False
>>> show $(qMatch [p| Just (Left _) |])
"Just (Left _)"

typed :: forall a b. (Typeable a, Typeable b) => Predicate a -> Predicate b Source #

Converts a Predicate to a new type. Typically used with visible type application, as in the examples below.

>>> accept (typed @String anything) "foo"
True
>>> accept (typed @String (sizeIs (gt 5))) "foo"
False
>>> accept (typed @String anything) (42 :: Int)
False

Multiplicity

data Multiplicity Source #

An acceptable range of number of times for something to happen.

A multiplicity can have a lower and an upper bound.

Instances

Instances details
Eq Multiplicity Source # 
Instance details

Defined in Test.HMock.Internal.Multiplicity

Num Multiplicity Source #

This is an incomplete instance, provided for convenience.

>>> meetsMultiplicity 5 4
False
>>> meetsMultiplicity 5 5
True
>>> between 4 6 - between 1 2
2 to 5 times
Instance details

Defined in Test.HMock.Internal.Multiplicity

Show Multiplicity Source # 
Instance details

Defined in Test.HMock.Internal.Multiplicity

meetsMultiplicity :: Multiplicity -> Int -> Bool Source #

Checks whether a certain number satisfies the Multiplicity.

once :: Multiplicity Source #

A Multiplicity that means exactly once.

>>> meetsMultiplicity once 0
False
>>> meetsMultiplicity once 1
True
>>> meetsMultiplicity once 2
False

anyMultiplicity :: Multiplicity Source #

A Multiplicity that means any number of times. >>> meetsMultiplicity anyMultiplicity 0 True >>> meetsMultiplicity anyMultiplicity 1 True >>> meetsMultiplicity anyMultiplicity 10 True

atLeast :: Multiplicity -> Multiplicity Source #

A Multiplicity that means at least this many times.

>>> meetsMultiplicity (atLeast 2) 1
False
>>> meetsMultiplicity (atLeast 2) 2
True
>>> meetsMultiplicity (atLeast 2) 3
True

atMost :: Multiplicity -> Multiplicity Source #

A Multiplicity that means at most this many times.

>>> meetsMultiplicity (atMost 2) 1
True
>>> meetsMultiplicity (atMost 2) 2
True
>>> meetsMultiplicity (atMost 2) 3
False

between :: Multiplicity -> Multiplicity -> Multiplicity Source #

A Multiplicity that means any number in this interval, endpoints included. For example, between 2 3 means 2 or 3 times, while between n n is equivalent to n.

>>> meetsMultiplicity (between 2 3) 1
False
>>> meetsMultiplicity (between 2 3) 2
True
>>> meetsMultiplicity (between 2 3) 3
True
>>> meetsMultiplicity (between 2 3) 4
False

Implementing mocks

class Typeable cls => MockableBase (cls :: (Type -> Type) -> Constraint) where Source #

A base class for Monad subclasses whose methods can be mocked. You usually want to generate this instance using makeMockable, makeMockableBase, deriveMockable, or deriveMockableBase. It's just boilerplate.

Associated Types

data Action cls :: Symbol -> (Type -> Type) -> Type -> Type Source #

An action that is performed. This data type will have one constructor for each method.

data Matcher cls :: Symbol -> (Type -> Type) -> Type -> Type Source #

A specification for matching actions. The actual arguments should be replaced with predicates.

Methods

showAction :: Action cls name m a -> String Source #

Gets a text description of an Action, for use in error messages.

showMatcher :: Maybe (Action cls name m a) -> Matcher cls name m b -> String Source #

Gets a text description of a Matcher, for use in error messages.

matchAction :: Matcher cls name m a -> Action cls name m a -> MatchResult Source #

Attempts to match an Action with a Matcher.

class MockableBase cls => Mockable (cls :: (Type -> Type) -> Constraint) where Source #

A class for Monad subclasses whose methods can be mocked. This class augments MockableBase with a setup method that is run before HMock touches the Monad subclass for the first time. The default implementation does nothing, but you can derive your own instances that add setup behavior.

Minimal complete definition

Nothing

Methods

setupMockable :: (MonadIO m, Typeable m) => proxy cls -> MockT m () Source #

data MatchResult where Source #

The result of matching a Matcher a with an Action b. Because the types should already guarantee that the methods match, all that's left is to match arguments.

Constructors

NoMatch :: Int -> MatchResult

No match. The int is the number of arguments that don't match.

Match :: MatchResult

Match. Stores a witness to the equality of return types.

mockMethod :: forall cls name m r. (HasCallStack, MonadIO m, MockableMethod cls name m r, Default r) => Action cls name m r -> MockT m r Source #

Implements a method in a Mockable monad by delegating to the mock framework. If the method is called unexpectedly, an exception will be thrown. However, an expected invocation without a specified response will return the default value.

mockDefaultlessMethod :: forall cls name m r. (HasCallStack, MonadIO m, MockableMethod cls name m r) => Action cls name m r -> MockT m r Source #

Implements a method in a Mockable monad by delegating to the mock framework. If the method is called unexpectedly, an exception will be thrown. However, an expected invocation without a specified response will return undefined. This can be used in place of mockMethod when the return type has no default.