mockcat 0.1.0.0 → 0.2.0.0
raw patch · 8 files changed
+291/−48 lines, 8 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Test.MockCat.Mock: createNamedStubFun :: MockBuilder params fun verifyParams => MonadIO m => String -> params -> m fun
+ Test.MockCat.AssociationList: (!?) :: Eq k => AssociationList k a -> k -> Maybe a
+ Test.MockCat.AssociationList: empty :: AssociationList k a
+ Test.MockCat.AssociationList: insert :: Eq k => k -> a -> AssociationList k a -> AssociationList k a
+ Test.MockCat.AssociationList: lookup :: Eq k => k -> AssociationList k a -> Maybe a
+ Test.MockCat.AssociationList: member :: Eq k => k -> AssociationList k a -> Bool
+ Test.MockCat.AssociationList: type AssociationList k a = [(k, a)]
+ Test.MockCat.AssociationList: update :: Eq k => (a -> a) -> k -> AssociationList k a -> AssociationList k a
+ Test.MockCat.Mock: createNamedStubFn :: MockBuilder params fun verifyParams => MonadIO m => String -> params -> m fun
Files
- README.md +49/−0
- mockcat.cabal +3/−1
- src/Test/MockCat/AssociationList.hs +34/−0
- src/Test/MockCat/Mock.hs +105/−46
- test/Spec.hs +2/−0
- test/Test/MockCat/AssociationListSpec.hs +48/−0
- test/Test/MockCat/ExampleSpec.hs +15/−1
- test/Test/MockCat/MockSpec.hs +35/−0
README.md view
@@ -179,6 +179,31 @@ f "b" `shouldBe` "return y" ``` +## Stub Function That Returns Different Values for the Same Arguments+When applying a list of x |> y pairs to the `createStubFn` function, you can create a stub function that returns different values for the same arguments by ensuring the return values differ for the same input arguments.+```haskell+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE TypeApplications #-}+import Test.Hspec+import Test.MockCat+import GHC.IO (evaluate)++spec :: Spec+spec = do+ it "Return different values for the same argument" do+ f <- createStubFn [+ "arg" |> "x",+ "arg" |> "y"+ ]+ -- Do not allow optimization to remove duplicates.+ v1 <- evaluate $ f "arg"+ v2 <- evaluate $ f "arg"+ v3 <- evaluate $ f "arg"+ v1 `shouldBe` "x"+ v2 `shouldBe` "y"+ v3 `shouldBe` "y" -- After the second time, “y” is returned.+```+ # Verification ## Verify if the Expected Arguments were Applied You can verify if the expected arguments were applied using the `shouldApplyTo` function.@@ -202,6 +227,30 @@ -- verify mock `shouldApplyTo` "value" ```+### Note+The recording of the application of arguments is done at the time the return value of the stub function is evaluated. +Therefore, verification must be done after evaluation.+```haskell+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE TypeApplications #-}+import Test.Hspec+import Test.MockCat++spec :: Spec+spec = do+ it "Verification does not work" do+ mock <- createMock $ "expect arg" |> "return value"+ -- Apply arguments to stub functions but do not evaluate values+ let _ = stubFn mock "expect arg"+ mock `shouldApplyTo` "expect arg"+```+```console+uncaught exception: ErrorCall+Expected arguments were not applied to the function.+ expected: "expect arg"+ but got: Never been called.+```+ ## Verify the Number of Times the Expected Arguments were Applied You can verify the number of times the expected arguments were applied using the `shouldApplyTimes` function. ```haskell
mockcat.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: mockcat-version: 0.1.0.0+version: 0.2.0.0 synopsis: Simple mock function library for test in Haskell. description: mockcat is simple mock library for test in Haskell. .@@ -41,6 +41,7 @@ library exposed-modules: Test.MockCat+ Test.MockCat.AssociationList Test.MockCat.Cons Test.MockCat.Mock Test.MockCat.Param@@ -61,6 +62,7 @@ type: exitcode-stdio-1.0 main-is: Spec.hs other-modules:+ Test.MockCat.AssociationListSpec Test.MockCat.ConsSpec Test.MockCat.ExampleSpec Test.MockCat.MockSpec
+ src/Test/MockCat/AssociationList.hs view
@@ -0,0 +1,34 @@+module Test.MockCat.AssociationList + (AssociationList, empty, insert, lookup, member, (!?), update) where++import Prelude hiding (lookup)+import Data.Maybe (isJust)++type AssociationList k a = [(k, a)]++empty :: AssociationList k a+empty = []++insert :: Eq k => k -> a -> AssociationList k a -> AssociationList k a+insert key value [] = [(key, value)]+insert key value ((k, v) : xs)+ | key == k = (key, value) : xs+ | otherwise = (k, v) : insert key value xs++lookup :: Eq k => k -> AssociationList k a -> Maybe a+lookup _ [] = Nothing+lookup key ((k, a) : xs)+ | key == k = Just a+ | otherwise = lookup key xs++member :: Eq k => k -> AssociationList k a -> Bool+member k list = isJust (lookup k list)++(!?) :: Eq k => AssociationList k a -> k -> Maybe a+(!?) = flip lookup++update :: Eq k => (a -> a) -> k -> AssociationList k a -> AssociationList k a+update f key list =+ case list !? key of+ Just value -> insert key (f value) list+ Nothing -> list
src/Test/MockCat/Mock.hs view
@@ -19,8 +19,8 @@ ( createMock, createNamedMock, createStubFn,- createNamedStubFun,- stubFn, + createNamedStubFn,+ stubFn, shouldApplyTo, shouldApplyTimes, shouldApplyInOrder,@@ -39,19 +39,21 @@ import Control.Monad.IO.Class (MonadIO (liftIO)) import Data.Function ((&)) import Data.IORef (IORef, modifyIORef', newIORef, readIORef)-import Data.List (elemIndex, find, intercalate)+import Data.List (elemIndex, intercalate) import Data.Maybe import Data.Text (pack, replace, unpack) import GHC.IO (unsafePerformIO) import Test.MockCat.Cons import Test.MockCat.Param import Test.MockCat.ParamDivider+import Test.MockCat.AssociationList (AssociationList, lookup, update, insert, empty, member)+import Prelude hiding (lookup) data Mock fun params = Mock (Maybe MockName) fun (Verifier params) type MockName = String -newtype Verifier params = Verifier (IORef (AppliedParamsList params))+newtype Verifier params = Verifier (IORef (AppliedRecord params)) {- | Create a mock. From this mock, you can generate stub functions and verify the functions.@@ -121,14 +123,15 @@ m fun createStubFn params = stubFn <$> createMock params + -- | Create a named stub function.-createNamedStubFun ::+createNamedStubFn :: MockBuilder params fun verifyParams => MonadIO m => String -> params -> m fun-createNamedStubFun name params = stubFn <$> createNamedMock name params+createNamedStubFn name params = stubFn <$> createNamedMock name params -- | Class for creating a mock corresponding to the parameter. class MockBuilder params fun verifyParams | params -> fun, params -> verifyParams where@@ -144,7 +147,7 @@ (Param a :> Param b :> Param c :> Param d :> Param e :> Param f :> Param g :> Param h :> Param i) where build name params = do- s <- liftIO $ newIORef ([] :: AppliedParamsList params)+ s <- liftIO $ newIORef appliedRecord makeMock name s@@ -160,7 +163,7 @@ (Param a :> Param b :> Param c :> Param d :> Param e :> Param f :> Param g :> Param h) where build name params = do- s <- liftIO $ newIORef ([] :: AppliedParamsList params)+ s <- liftIO $ newIORef appliedRecord makeMock name s@@ -176,7 +179,7 @@ (Param a :> Param b :> Param c :> Param d :> Param e :> Param f :> Param g) where build name params = do- s <- liftIO $ newIORef ([] :: AppliedParamsList params)+ s <- liftIO $ newIORef appliedRecord makeMock name s@@ -192,7 +195,7 @@ (Param a :> Param b :> Param c :> Param d :> Param e :> Param f) where build name params = do- s <- liftIO $ newIORef ([] :: AppliedParamsList params)+ s <- liftIO $ newIORef appliedRecord makeMock name s (\a2 b2 c2 d2 e2 f2 -> unsafePerformIO $ extractReturnValueWithValidate name params (p a2 :> p b2 :> p c2 :> p d2 :> p e2 :> p f2) s) instance@@ -203,7 +206,7 @@ (Param a :> Param b :> Param c :> Param d :> Param e) where build name params = do- s <- liftIO $ newIORef ([] :: AppliedParamsList params)+ s <- liftIO $ newIORef appliedRecord makeMock name s (\a2 b2 c2 d2 e2 -> unsafePerformIO $ extractReturnValueWithValidate name params (p a2 :> p b2 :> p c2 :> p d2 :> p e2) s) instance@@ -214,7 +217,7 @@ (Param a :> Param b :> Param c :> Param d) where build name params = do- s <- liftIO $ newIORef ([] :: AppliedParamsList params)+ s <- liftIO $ newIORef appliedRecord makeMock name s (\a2 b2 c2 d2 -> unsafePerformIO $ extractReturnValueWithValidate name params (p a2 :> p b2 :> p c2 :> p d2) s) instance@@ -222,7 +225,7 @@ MockBuilder (Param a :> Param b :> Param c :> Param r) (a -> b -> c -> r) (Param a :> Param b :> Param c) where build name params = do- s <- liftIO $ newIORef ([] :: AppliedParamsList params)+ s <- liftIO $ newIORef appliedRecord makeMock name s (\a2 b2 c2 -> unsafePerformIO $ extractReturnValueWithValidate name params (p a2 :> p b2 :> p c2) s) instance@@ -230,7 +233,7 @@ MockBuilder (Param a :> Param b :> Param r) (a -> b -> r) (Param a :> Param b) where build name params = do- s <- liftIO $ newIORef ([] :: AppliedParamsList params)+ s <- liftIO $ newIORef appliedRecord makeMock name s (\a2 b2 -> unsafePerformIO $ extractReturnValueWithValidate name params (p a2 :> p b2) s) instance@@ -238,7 +241,7 @@ MockBuilder (Param a :> Param r) (a -> r) (Param a) where build name params = do- s <- liftIO $ newIORef ([] :: AppliedParamsList params)+ s <- liftIO $ newIORef appliedRecord makeMock name s (\a2 -> unsafePerformIO $ extractReturnValueWithValidate name params (p a2) s) instance@@ -249,7 +252,7 @@ (Param a :> Param b :> Param c :> Param d :> Param e :> Param f :> Param g :> Param h :> Param i) where build name params = do- s <- liftIO $ newIORef ([] :: AppliedParamsList params)+ s <- liftIO $ newIORef appliedRecord makeMock name s (\a2 b2 c2 d2 e2 f2 g2 h2 i2 -> unsafePerformIO $ findReturnValueWithStore name params (p a2 :> p b2 :> p c2 :> p d2 :> p e2 :> p f2 :> p g2 :> p h2 :> p i2) s) instance@@ -260,7 +263,7 @@ (Param a :> Param b :> Param c :> Param d :> Param e :> Param f :> Param g :> Param h) where build name params = do- s <- liftIO $ newIORef ([] :: AppliedParamsList params)+ s <- liftIO $ newIORef appliedRecord makeMock name s (\a2 b2 c2 d2 e2 f2 g2 h2 -> unsafePerformIO $ findReturnValueWithStore name params (p a2 :> p b2 :> p c2 :> p d2 :> p e2 :> p f2 :> p g2 :> p h2) s) instance@@ -271,7 +274,7 @@ (Param a :> Param b :> Param c :> Param d :> Param e :> Param f :> Param g) where build name params = do- s <- liftIO $ newIORef ([] :: AppliedParamsList params)+ s <- liftIO $ newIORef appliedRecord makeMock name s (\a2 b2 c2 d2 e2 f2 g2 -> unsafePerformIO $ findReturnValueWithStore name params (p a2 :> p b2 :> p c2 :> p d2 :> p e2 :> p f2 :> p g2) s) instance@@ -282,7 +285,7 @@ (Param a :> Param b :> Param c :> Param d :> Param e :> Param f) where build name params = do- s <- liftIO $ newIORef ([] :: AppliedParamsList params)+ s <- liftIO $ newIORef appliedRecord makeMock name s (\a2 b2 c2 d2 e2 f2 -> unsafePerformIO $ findReturnValueWithStore name params (p a2 :> p b2 :> p c2 :> p d2 :> p e2 :> p f2) s) instance@@ -293,7 +296,7 @@ (Param a :> Param b :> Param c :> Param d :> Param e) where build name params = do- s <- liftIO $ newIORef ([] :: AppliedParamsList params)+ s <- liftIO $ newIORef appliedRecord makeMock name s (\a2 b2 c2 d2 e2 -> unsafePerformIO $ findReturnValueWithStore name params (p a2 :> p b2 :> p c2 :> p d2 :> p e2) s) instance@@ -304,7 +307,7 @@ (Param a :> Param b :> Param c :> Param d) where build name params = do- s <- liftIO $ newIORef ([] :: AppliedParamsList params)+ s <- liftIO $ newIORef appliedRecord makeMock name s (\a2 b2 c2 d2 -> unsafePerformIO $ findReturnValueWithStore name params (p a2 :> p b2 :> p c2 :> p d2) s) instance@@ -315,7 +318,7 @@ (Param a :> Param b :> Param c) where build name params = do- s <- liftIO $ newIORef ([] :: AppliedParamsList params)+ s <- liftIO $ newIORef appliedRecord makeMock name s (\a2 b2 c2 -> unsafePerformIO $ findReturnValueWithStore name params (p a2 :> p b2 :> p c2) s) instance@@ -323,7 +326,7 @@ MockBuilder [Param a :> Param b :> Param r] (a -> b -> r) (Param a :> Param b) where build name params = do- s <- liftIO $ newIORef ([] :: AppliedParamsList params)+ s <- liftIO $ newIORef appliedRecord makeMock name s (\a2 b2 -> unsafePerformIO $ findReturnValueWithStore name params (p a2 :> p b2) s) instance@@ -331,7 +334,7 @@ MockBuilder [Param a :> Param r] (a -> r) (Param a) where build name params = do- s <- liftIO $ newIORef ([] :: AppliedParamsList params)+ s <- liftIO $ newIORef appliedRecord makeMock name s (\a2 -> unsafePerformIO $ findReturnValueWithStore name params (p a2) s) -- ------@@ -339,11 +342,9 @@ p :: a -> Param a p = param -makeMock :: MonadIO m => Maybe MockName -> IORef (AppliedParamsList params) -> fun -> m (Mock fun params)+makeMock :: MonadIO m => Maybe MockName -> IORef (AppliedRecord params) -> fun -> m (Mock fun params) makeMock name l fn = pure $ Mock name fn (Verifier l) -type AppliedParamsList params = [params]- extractReturnValueWithValidate :: ParamDivider params args (Param r) => Eq args =>@@ -351,7 +352,7 @@ Maybe MockName -> params -> args ->- IORef (AppliedParamsList args) ->+ IORef (AppliedRecord args) -> IO r extractReturnValueWithValidate name params inputParams s = do validateWithStoreParams name s (args params) inputParams@@ -364,12 +365,12 @@ Maybe MockName -> AppliedParamsList params -> args ->- IORef (AppliedParamsList args) ->+ IORef (AppliedRecord args) -> IO r findReturnValueWithStore name paramsList inputParams ref = do- modifyIORef' ref (++ [inputParams])+ appendAppliedParams ref inputParams let expectedArgs = args <$> paramsList- r = findReturnValue paramsList inputParams+ r <- findReturnValue paramsList inputParams ref maybe (errorWithoutStackTrace $ messageForMultiMock name expectedArgs inputParams) pure@@ -380,15 +381,22 @@ ParamDivider params args (Param r) => AppliedParamsList params -> args ->- Maybe r-findReturnValue paramsList inputParams = do- find (\params -> args params == inputParams) paramsList- >>= \params -> pure $ returnValue params+ IORef (AppliedRecord args) ->+ IO (Maybe r)+findReturnValue paramsList inputParams ref = do+ let matchedParams = filter (\params -> args params == inputParams) paramsList+ case matchedParams of+ [] -> pure Nothing+ _ -> do+ count <- readAppliedCount ref inputParams+ let index = min count (length matchedParams - 1)+ incrementAppliedParamCount ref inputParams+ pure $ returnValue <$> safeIndex matchedParams index -validateWithStoreParams :: (Eq a, Show a) => Maybe MockName -> IORef (AppliedParamsList a) -> a -> a -> IO ()+validateWithStoreParams :: (Eq a, Show a) => Maybe MockName -> IORef (AppliedRecord a) -> a -> a -> IO () validateWithStoreParams name ref expected actual = do validateParams name expected actual- modifyIORef' ref (++ [actual])+ appendAppliedParams ref actual validateParams :: (Eq a, Show a) => Maybe MockName -> a -> a -> IO () validateParams name expected actual =@@ -438,7 +446,7 @@ verify :: (Eq params, Show params) => Mock fun params -> VerifyMatchType params -> IO () verify (Mock name _ (Verifier ref)) matchType = do- calledParamsList <- readIORef ref+ calledParamsList <- readAppliedParamsList ref let result = doVerify name calledParamsList matchType result & maybe (pure ()) (\(VerifyFailed msg) -> errorWithoutStackTrace msg) @@ -524,7 +532,7 @@ verifyCount :: Eq params => Mock fun params -> params -> CountVerifyMethod -> IO () verifyCount (Mock name _ (Verifier ref)) v method = do- calledParamsList <- readIORef ref+ calledParamsList <- readAppliedParamsList ref let callCount = length (filter (v ==) calledParamsList) if compareCount method callCount then pure ()@@ -583,17 +591,17 @@ [params] -> IO () verifyOrder method (Mock name _ (Verifier ref)) matchers = do- calledParamsList <- readIORef ref+ calledParamsList <- readAppliedParamsList ref let result = doVerifyOrder method name calledParamsList matchers result & maybe (pure ()) (\(VerifyFailed msg) -> errorWithoutStackTrace msg) -doVerifyOrder :: +doVerifyOrder :: Eq a =>- Show a => - VerifyOrderMethod -> - Maybe MockName -> - AppliedParamsList a -> - [a] -> + Show a =>+ VerifyOrderMethod ->+ Maybe MockName ->+ AppliedParamsList a ->+ [a] -> Maybe VerifyFailed doVerifyOrder ExactlySequence name calledValues expectedValues | length calledValues /= length expectedValues = do@@ -721,3 +729,54 @@ a -> IO () shouldApplyTimesLessThan m i = shouldApplyTimes m (LessThan i)++++type AppliedParamsList params = [params]+type AppliedParamsCounter params = AssociationList params Int++data AppliedRecord params = AppliedRecord {+ appliedParamsList :: AppliedParamsList params,+ appliedParamsCounter :: AppliedParamsCounter params+}++appliedRecord :: AppliedRecord params+appliedRecord = AppliedRecord {+ appliedParamsList = mempty,+ appliedParamsCounter = empty+}++readAppliedParamsList :: IORef (AppliedRecord params) -> IO (AppliedParamsList params)+readAppliedParamsList ref = do+ record <- readIORef ref+ pure $ appliedParamsList record++readAppliedCount :: Eq params => IORef (AppliedRecord params) -> params -> IO Int+readAppliedCount ref params = do+ record <- readIORef ref+ let count = appliedParamsCounter record+ pure $ fromMaybe 0 (lookup params count)++appendAppliedParams :: IORef (AppliedRecord params) -> params -> IO ()+appendAppliedParams ref inputParams = do+ modifyIORef' ref (\AppliedRecord {appliedParamsList, appliedParamsCounter} -> AppliedRecord {+ appliedParamsList = appliedParamsList ++ [inputParams],+ appliedParamsCounter = appliedParamsCounter+ })++incrementAppliedParamCount ::Eq params => IORef (AppliedRecord params) -> params -> IO ()+incrementAppliedParamCount ref inputParams = do+ modifyIORef' ref (\AppliedRecord {appliedParamsList, appliedParamsCounter} -> AppliedRecord {+ appliedParamsList = appliedParamsList,+ appliedParamsCounter = incrementCount inputParams appliedParamsCounter+ })++incrementCount :: Eq k => k -> AppliedParamsCounter k -> AppliedParamsCounter k+incrementCount key list =+ if member key list then update (+ 1) key list+ else insert key 1 list++safeIndex :: [a] -> Int -> Maybe a+safeIndex xs n+ | n < 0 = Nothing+ | otherwise = listToMaybe (drop n xs)
test/Spec.hs view
@@ -2,6 +2,7 @@ import Test.MockCat.MockSpec as Mock import Test.MockCat.ConsSpec as Cons import Test.MockCat.ParamSpec as Param+import Test.MockCat.AssociationListSpec as AssociationList import Test.MockCat.ExampleSpec as Example main :: IO ()@@ -10,4 +11,5 @@ Cons.spec Param.spec Mock.spec+ AssociationList.spec Example.spec
+ test/Test/MockCat/AssociationListSpec.hs view
@@ -0,0 +1,48 @@+{-# LANGUAGE BlockArguments #-}+{-# OPTIONS_GHC -Wno-type-defaults #-}+module Test.MockCat.AssociationListSpec (spec) where++import Test.Hspec+import Test.MockCat.AssociationList (AssociationList, empty, insert, update, (!?), member)++spec :: Spec+spec = do+ it "empty" do+ let e = empty :: AssociationList String Int+ e `shouldBe` []++ describe "insert" do+ it "not exist" do+ let l = empty :: AssociationList String Int+ insert "key" 100 l `shouldBe` [("key", 100)]++ it "exist" do+ let l = insert "key" 100 empty :: AssociationList String Int+ insert "key" 120 l `shouldBe` [("key", 120)]+ + describe "update" do+ it "not exist" do+ let l = empty :: AssociationList String Int+ update (+ 20) "key" l `shouldBe` []++ it "exist" do+ let l = insert "key" 100 empty :: AssociationList String Int+ update (+ 20) "key" l `shouldBe` [("key", 120)]+ + describe "lookup" do+ it "not exist" do+ let l = empty :: AssociationList String Int+ l !? "key" `shouldBe` Nothing++ it "exist" do+ let l = insert "key" 100 empty :: AssociationList String Int+ l !? "key" `shouldBe` Just 100++ describe "member" do+ it "not exist" do+ let l = empty :: AssociationList String Int+ member "key" l `shouldBe` False++ it "exist" do+ let l = insert "key" 100 empty :: AssociationList String Int+ member "key" l `shouldBe` True
test/Test/MockCat/ExampleSpec.hs view
@@ -7,6 +7,7 @@ import Test.Hspec import Test.MockCat import Prelude hiding (and, any, not, or)+import GHC.IO (evaluate) spec :: Spec spec = do@@ -26,7 +27,7 @@ actual `shouldBe` () it "named stub" do- f <- createNamedStubFun "named stub" $ "x" |> "y" |> True+ f <- createNamedStubFn "named stub" $ "x" |> "y" |> True f "x" "y" `shouldBe` True it "named mock" do@@ -86,3 +87,16 @@ ] f "a" `shouldBe` "return x" f "b" `shouldBe` "return y"++ it "Return different values for the same argument" do+ f <- createStubFn [+ "arg" |> "x",+ "arg" |> "y"+ ]+ -- Do not allow optimization to remove duplicates.+ v1 <- evaluate $ f "arg"+ v2 <- evaluate $ f "arg"+ v3 <- evaluate $ f "arg"+ v1 `shouldBe` "x"+ v2 `shouldBe` "y"+ v3 `shouldBe` "y" -- After the second time, “y” is returned.
test/Test/MockCat/MockSpec.hs view
@@ -793,6 +793,41 @@ it "expectByExpr" do f <- createStubFn $ $(expectByExpr [|\x -> x == "y" || x == "z"|]) |> True f "y" `shouldBe` True+ + describe "repeatable" do+ it "arity = 1" do+ f <- createStubFn [+ "a" |> True,+ "b" |> False,+ "a" |> False,+ "b" |> True+ ]+ v1 <- evaluate $ f "a"+ v2 <- evaluate $ f "a"+ v3 <- evaluate $ f "b"+ v4 <- evaluate $ f "b"+ v1 `shouldBe` True+ v2 `shouldBe` False+ v3 `shouldBe` False+ v4 `shouldBe` True++ it "arity = 2" do+ f <- createStubFn [+ "a" |> "b" |> (0 :: Int),+ "a" |> "c" |> (1 :: Int),+ "a" |> "b" |> (2 :: Int),+ "a" |> "c" |> (3 :: Int)+ ]+ v1 <- evaluate $ f "a" "b"+ v2 <- evaluate $ f "a" "b"+ v3 <- evaluate $ f "a" "c"+ v4 <- evaluate $ f "a" "c"+ v5 <- evaluate $ f "a" "b"+ v1 `shouldBe` (0 :: Int)+ v2 `shouldBe` (2 :: Int)+ v3 `shouldBe` (1 :: Int)+ v4 `shouldBe` (3 :: Int)+ v5 `shouldBe` (2 :: Int) data Fixture mock r = Fixture { name :: String,