mockcat-1.2.1.0: test/Test/MockCat/TypeClassCommonSpec.hs
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# OPTIONS_GHC -Wno-orphans #-}
{-# OPTIONS_GHC -Wno-name-shadowing #-}
{-# OPTIONS_GHC -Wno-missing-export-lists #-}
{- HLINT ignore "Use newtype instead of data" -}
module Test.MockCat.TypeClassCommonSpec where
import Prelude hiding (readFile, writeFile, any)
import Test.Hspec
import Test.MockCat
import Data.Kind (Type)
import Data.Text (Text, pack, isInfixOf)
import Control.Exception (ErrorCall(..), displayException)
import Control.Monad.IO.Class (MonadIO(..))
import Control.Monad.Reader (MonadReader, ask)
import Control.Monad.State (MonadState(..), StateT, evalStateT)
import Control.Monad.Trans.Class (lift)
import Test.MockCat.SharedSpecDefs
import qualified Data.List as List
import Control.Concurrent.Async (async, wait)
import Control.Monad.IO.Unlift (withRunInIO)
import Control.Monad (unless)
-- Helpers
missingCall :: String -> Selector ErrorCall
missingCall name err =
let needle1 = "function `" <> name <> "` was not called the expected number of times."
needle2 = "function `_" <> name <> "` was not called the expected number of times."
in (needle1 `List.isInfixOf` displayException err) || (needle2 `List.isInfixOf` displayException err)
-- Orphan Instances needed for testing
instance MonadState s m => MonadState s (MockT m) where
get = lift get
put = lift . put
state f = lift (state f)
-- Programs under test
data Environment = Environment { inputPath :: String, outputPath :: String }
deriving (Show, Eq)
apiFileOperationProgram ::
(MonadReader Environment m, FileOperation m, ApiOperation m) =>
(Text -> Text) ->
m ()
apiFileOperationProgram modifyText = do
e <- ask
content <- readFile (inputPath e)
let modifiedContent = modifyText content
writeFile (outputPath e) modifiedContent
post $ modifiedContent <> pack ("+" <> show e)
operationProgram ::
(FileOperation m) =>
FilePath ->
FilePath ->
m ()
operationProgram inputPath outputPath = do
content <- readFile inputPath
unless (pack "ngWord" `isInfixOf` content) $
writeFile outputPath content
operationProgram2 ::
(FileOperation m, ApiOperation m) =>
FilePath ->
FilePath ->
(Text -> Text) ->
m ()
operationProgram2 inputPath outputPath modifyText = do
content <- readFile inputPath
let modifiedContent = modifyText content
writeFile outputPath modifiedContent
post modifiedContent
operationProgram3 ::
MonadReader Environment m =>
FileOperation m =>
m ()
operationProgram3 = do
(Environment inputPath outputPath) <- ask
content <- readFile inputPath
writeFile outputPath content
echoProgram :: (MonadIO m, TestClass m) => String -> m ()
echoProgram s = do
v <- getBy s
liftIO $ print v
echo $ show v
echoProgramExplicit :: ExplicitlyReturnMonadicValuesTest m => String -> m ()
echoProgramExplicit s = do
v <- getByExplicit s
echoExplicit $ show v
processFiles :: (MonadAsync m, FileOperation m) => [FilePath] -> m [Text]
processFiles = mapConcurrently readFile
echo2 :: Teletype m => m ()
echo2 = do
i <- readTTY
case i of
"" -> pure ()
_ -> writeTTY i >> echo2
-- Specs
-- Type-level utilities to derive MockBuilder arg-lists from function types
type family PrependParam (p :: Type) (rest :: Type) :: Type where
PrependParam p () = p
PrependParam p rest = p :> rest
type family ArgsOfF (f :: Type) :: Type where
ArgsOfF (a -> b) = PrependParam (Param a) (ArgsOfF b)
ArgsOfF r = ()
-- Generic Mock alias for a function type f
type MockFor f = forall params. (MockDispatch (IsMockSpec params) params (MockT IO) f) => params -> MockT IO f
-- Generic Mock alias for an arbitrary base monad m
type MockForM m f = forall params. (MockDispatch (IsMockSpec params) params (MockT m) f) => params -> MockT m f
-- Per-spec dependency records to group required builders/mocks
data BasicDeps = BasicDeps
{ _readFile :: MockFor (FilePath -> Text)
, _writeFile :: MockFor (FilePath -> Text -> ())
}
data MixedDeps = MixedDeps
{ _readFile :: MockFor (FilePath -> Text)
, _writeFile :: MockFor (FilePath -> Text -> ())
, _post :: MockFor (Text -> ())
}
data MultipleDeps = MultipleDeps
{ _ask :: MockFor Environment
, _readFile :: MockFor (FilePath -> Text)
, _writeFile:: MockFor (FilePath -> Text -> ())
, _post :: MockFor (Text -> ())
}
data ReaderContextDeps = ReaderContextDeps
{ _ask :: MockFor Environment
, _readFile :: MockFor (FilePath -> Text)
, _writeFile:: MockFor (FilePath -> Text -> ())
}
data SequentialIODeps = SequentialIODeps
{ _readTTY :: MockFor (IO String)
, _writeTTY :: MockFor (String -> IO ())
}
data ImplicitMonadicReturnDeps = ImplicitMonadicReturnDeps
{ _getBy :: MockFor (String -> IO Int)
, _echo :: MockFor (String -> IO ())
}
data ArgumentPatternMatchingDeps = ArgumentPatternMatchingDeps
{ _getValueBy :: MockFor (String -> IO String)
}
data MonadStateTransformerDeps = MonadStateTransformerDeps
{ _fnState :: MockForM (StateT String IO) (Maybe String -> StateT String IO String)
, _fnState2 :: MockForM (StateT String IO) (String -> StateT String IO ())
}
data MultiParamTypeClassArityDeps = MultiParamTypeClassArityDeps
{ _fn2_1Sub :: MockFor (String -> IO ())
, _fn2_2Sub :: MockFor (String -> IO ())
, _fn3_1Sub :: MockFor (String -> IO ())
, _fn3_2Sub :: MockFor (String -> IO ())
, _fn3_3Sub :: MockFor (String -> IO ())
}
data FunctionalDependenciesDeps = FunctionalDependenciesDeps
{ _fnParam3_1 :: MockFor (Int -> Bool -> IO String)
, _fnParam3_2 :: MockFor (IO Int)
, _fnParam3_3 :: MockFor (IO Bool)
}
data ExplicitMonadicReturnDeps = ExplicitMonadicReturnDeps
{ _getByExplicit :: MockFor (String -> IO Int)
, _echoExplicit :: MockFor (String -> IO ())
}
data DefaultMethodDeps = DefaultMethodDeps
{ _defaultAction :: MockFor Int
}
data AssociatedTypeFamiliesDeps = AssociatedTypeFamiliesDeps
{ _produce :: MockFor Int
}
data ConcurrencyAndUnliftIODeps = ConcurrencyAndUnliftIODeps
{ _readFile :: MockFor (FilePath -> Text)
}
data UserDefinedTypeDeps = UserDefinedTypeDeps
{ _processPost :: MockFor (Post -> Bool)
}
-- backward-compatible constructor pattern synonyms (preserve old names used by test modules)
pattern TtyDeps :: MockFor (IO String) -> MockFor (String -> IO ()) -> SequentialIODeps
pattern TtyDeps r w <- SequentialIODeps { _readTTY = r, _writeTTY = w }
where TtyDeps r w = SequentialIODeps { _readTTY = r, _writeTTY = w }
pattern TestClassDeps :: MockFor (String -> IO Int) -> MockFor (String -> IO ()) -> ImplicitMonadicReturnDeps
pattern TestClassDeps g e <- ImplicitMonadicReturnDeps { _getBy = g, _echo = e }
where TestClassDeps g e = ImplicitMonadicReturnDeps { _getBy = g, _echo = e }
pattern MultiApplyDeps :: MockFor (String -> IO String) -> ArgumentPatternMatchingDeps
pattern MultiApplyDeps f <- ArgumentPatternMatchingDeps { _getValueBy = f }
where MultiApplyDeps f = ArgumentPatternMatchingDeps { _getValueBy = f }
pattern StateDeps :: MockForM (StateT String IO) (Maybe String -> StateT String IO String) -> MockForM (StateT String IO) (String -> StateT String IO ()) -> MonadStateTransformerDeps
pattern StateDeps s s2 <- MonadStateTransformerDeps { _fnState = s, _fnState2 = s2 }
where StateDeps s s2 = MonadStateTransformerDeps { _fnState = s, _fnState2 = s2 }
pattern MultiParamDeps :: MockFor (String -> IO ()) -> MockFor (String -> IO ()) -> MockFor (String -> IO ()) -> MockFor (String -> IO ()) -> MockFor (String -> IO ()) -> MultiParamTypeClassArityDeps
pattern MultiParamDeps a b c d e <- MultiParamTypeClassArityDeps { _fn2_1Sub = a, _fn2_2Sub = b, _fn3_1Sub = c, _fn3_2Sub = d, _fn3_3Sub = e }
where MultiParamDeps a b c d e = MultiParamTypeClassArityDeps { _fn2_1Sub = a, _fn2_2Sub = b, _fn3_1Sub = c, _fn3_2Sub = d, _fn3_3Sub = e }
pattern FunDeps :: MockFor (Int -> Bool -> IO String) -> MockFor (IO Int) -> MockFor (IO Bool) -> FunctionalDependenciesDeps
pattern FunDeps a b c <- FunctionalDependenciesDeps { _fnParam3_1 = a, _fnParam3_2 = b, _fnParam3_3 = c }
where FunDeps a b c = FunctionalDependenciesDeps { _fnParam3_1 = a, _fnParam3_2 = b, _fnParam3_3 = c }
pattern ExplicitReturnDeps :: MockFor (String -> IO Int) -> MockFor (String -> IO ()) -> ExplicitMonadicReturnDeps
pattern ExplicitReturnDeps a b <- ExplicitMonadicReturnDeps { _getByExplicit = a, _echoExplicit = b }
where ExplicitReturnDeps a b = ExplicitMonadicReturnDeps { _getByExplicit = a, _echoExplicit = b }
pattern AssocTypeDeps :: MockFor Int -> AssociatedTypeFamiliesDeps
pattern AssocTypeDeps f <- AssociatedTypeFamiliesDeps { _produce = f }
where AssocTypeDeps f = AssociatedTypeFamiliesDeps { _produce = f }
pattern ConcurrencyDeps :: MockFor (FilePath -> Text) -> ConcurrencyAndUnliftIODeps
pattern ConcurrencyDeps r <- ConcurrencyAndUnliftIODeps { _readFile = r }
where ConcurrencyDeps r = ConcurrencyAndUnliftIODeps { _readFile = r }
-- Aggregate all per-spec dependency groups + standalone mocks into one record
data SpecDeps = SpecDeps
{ basicDeps :: BasicDeps
, mixedDeps :: MixedDeps
, multipleDeps :: MultipleDeps
, readerContextDeps :: ReaderContextDeps
, sequentialIODeps :: SequentialIODeps
, ttyDeps :: SequentialIODeps
, implicitMonadicReturnDeps :: ImplicitMonadicReturnDeps
, testClassDeps :: ImplicitMonadicReturnDeps
, argumentPatternMatchingDeps :: ArgumentPatternMatchingDeps
, multiApplyDeps :: ArgumentPatternMatchingDeps
, monadStateTransformerDeps :: MonadStateTransformerDeps
, stateDeps :: MonadStateTransformerDeps
, multiParamTypeClassArityDeps :: MultiParamTypeClassArityDeps
, multiParamDeps :: MultiParamTypeClassArityDeps
, functionalDependenciesDeps :: FunctionalDependenciesDeps
, funDeps :: FunctionalDependenciesDeps
, explicitMonadicReturnDeps :: ExplicitMonadicReturnDeps
, explicitReturnDeps :: ExplicitMonadicReturnDeps
, defaultMethodDeps :: DefaultMethodDeps
, assocTypeDeps :: AssociatedTypeFamiliesDeps
, associatedTypeFamiliesDeps :: AssociatedTypeFamiliesDeps
, concurrencyAndUnliftIODeps :: ConcurrencyAndUnliftIODeps
, concurrencyDeps :: ConcurrencyAndUnliftIODeps
, userDefinedTypeDeps :: UserDefinedTypeDeps
}
-- SpecDeps is defined above; test modules construct a `SpecDeps` and call the individual specs
-- Aggregated entry point: call all specs from a single SpecDeps
spec ::
( Teletype (MockT IO)
, FileOperation (MockT IO)
, ApiOperation (MockT IO)
, MonadReader Environment (MockT IO)
, TestClass (MockT IO)
, MultiApplyTest (MockT IO)
, MonadVar2_1Sub (MockT IO) String
, MonadVar2_2Sub String (MockT IO)
, MonadVar3_1Sub (MockT IO) String String
, MonadVar3_2Sub String (MockT IO) String
, MonadVar3_3Sub String String (MockT IO)
, MonadStateSub String (MockT (StateT String IO))
, MonadStateSub2 String (MockT (StateT String IO))
, ParamThreeMonad Int Bool (MockT IO)
, ExplicitlyReturnMonadicValuesTest (MockT IO)
, DefaultMethodTest (MockT IO)
, AssocTypeTest (MockT IO)
, ResultType (MockT IO) ~ Int
, MonadAsync (MockT IO)
, UserDefinedClass (MockT IO)
) =>
SpecDeps ->
Spec
spec deps = do
specSequentialIOStubbing deps.sequentialIODeps
specBasicStubbingAndVerification deps.basicDeps
specMixedMockingStrategies deps.mixedDeps
specMultipleTypeclassConstraints deps.multipleDeps
specImplicitMonadicReturnValues deps.implicitMonadicReturnDeps
specArgumentPatternMatching deps.argumentPatternMatchingDeps
specMultiParamTypeClassArity deps.multiParamTypeClassArityDeps
specMonadStateTransformerSupport deps.monadStateTransformerDeps
specFunctionalDependenciesSupport deps.functionalDependenciesDeps
specExplicitMonadicReturnValues deps.explicitMonadicReturnDeps
specDefaultMethodMocking deps.defaultMethodDeps
specAssociatedTypeFamiliesSupport deps.associatedTypeFamiliesDeps
specConcurrencyAndUnliftIO deps.concurrencyAndUnliftIODeps
specMonadReaderContextMocking deps.readerContextDeps
specUserDefinedTypeSupport deps.userDefinedTypeDeps
-- Verification failures
specBasicVerificationFailureDetection deps.basicDeps
specMonadReaderVerificationFailureDetection deps.readerContextDeps
specImplicitReturnVerificationFailureDetection deps.implicitMonadicReturnDeps
specMultiParamVerificationFailureDetection deps.multiParamTypeClassArityDeps
specArgumentMatchingVerificationFailureDetection deps.argumentPatternMatchingDeps
specFunDepsVerificationFailureDetection deps.functionalDependenciesDeps
specExplicitReturnVerificationFailureDetection deps.explicitMonadicReturnDeps
specAdvancedTypesVerificationFailureDetection deps.defaultMethodDeps deps.associatedTypeFamiliesDeps
specSequentialStubbingVerificationFailureDetection deps.sequentialIODeps
specBasicStubbingAndVerification ::
( FileOperation (MockT IO)
) =>
BasicDeps ->
Spec
specBasicStubbingAndVerification (BasicDeps { _readFile, _writeFile }) = do
it "Program reads content and writes it to output path" do
result <- runMockT do
_ <- _readFile $ "input.txt" ~> pack "content"
_ <- _writeFile $ "output.txt" ~> pack "content" ~> ()
operationProgram "input.txt" "output.txt"
result `shouldBe` ()
it "Program skips file write when input content contains 'ngWord'" do
result <- runMockT do
_ <- _readFile ("input.txt" ~> pack "contains ngWord")
_ <- _writeFile ("output.txt" ~> any ~> ())
`expects` do
called never
operationProgram "input.txt" "output.txt"
result `shouldBe` ()
specMixedMockingStrategies ::
( FileOperation (MockT IO)
, ApiOperation (MockT IO)
) =>
MixedDeps ->
Spec
specMixedMockingStrategies (MixedDeps { _readFile, _writeFile, _post }) = do
it "Program reads, modifies content via stub, writes, and posts result" do
modifyContentStub <- mock $ pack "content" ~> pack "modifiedContent"
result <- runMockT do
_ <- _readFile $ "input.txt" ~> pack "content"
_ <- _writeFile $ ("output.text" ~> pack "modifiedContent" ~> ())
_ <- _post $ (pack "modifiedContent" ~> ())
operationProgram2 "input.txt" "output.text" modifyContentStub
result `shouldBe` ()
specMultipleTypeclassConstraints ::
( FileOperation (MockT IO)
, ApiOperation (MockT IO)
, MonadReader Environment (MockT IO)
) =>
MultipleDeps ->
Spec
specMultipleTypeclassConstraints (MultipleDeps { _ask, _readFile, _writeFile, _post }) = do
it "Composed program successfully reads environment, processes file, and posts combined result" do
modifyContentStub <- mock $ pack "content" ~> pack "modifiedContent"
let env = Environment "input.txt" "output.text"
result <- runMockT do
_ <- _ask env
_ <- _readFile ("input.txt" ~> pack "content")
_ <- _writeFile ("output.text" ~> pack "modifiedContent" ~> ())
_ <- _post (pack "modifiedContent" <> pack ("+" <> show env) ~> ())
apiFileOperationProgram modifyContentStub
result `shouldBe` ()
specMonadReaderContextMocking ::
( MonadReader Environment (MockT IO)
, FileOperation (MockT IO)
) =>
ReaderContextDeps ->
Spec
specMonadReaderContextMocking (ReaderContextDeps { _ask, _readFile, _writeFile }) = do
it "Program successfully uses MonadReader to find paths and executes FileOperation" do
r <- runMockT do
_ <- _ask (Environment "input.txt" "output.txt")
_ <- _readFile ("input.txt" ~> pack "content")
_ <- _writeFile ("output.txt" ~> pack "content" ~> ())
operationProgram3
r `shouldBe` ()
specSequentialIOStubbing ::
( Teletype (MockT IO)
) =>
SequentialIODeps ->
Spec
specSequentialIOStubbing (SequentialIODeps { _readTTY, _writeTTY }) = do
it "Recursive program echo2 reads sequential input and writes output until empty string" do
result <- runMockT do
_ <- _readTTY $ casesIO ["a", ""]
_ <- _writeTTY $ "a" ~> pure @IO ()
echo2
result `shouldBe` ()
specImplicitMonadicReturnValues ::
( TestClass (MockT IO)
) =>
ImplicitMonadicReturnDeps ->
Spec
specImplicitMonadicReturnValues (ImplicitMonadicReturnDeps { _getBy, _echo }) = do
it "Program correctly uses and echoes the implicitly stubbed monadic return value" do
result <- runMockT do
_ <- _getBy $ "s" ~> pure @IO (10 :: Int)
_ <- _echo $ "10" ~> pure @IO ()
echoProgram "s"
result `shouldBe` ()
specArgumentPatternMatching ::
( MultiApplyTest (MockT IO)
) =>
ArgumentPatternMatchingDeps ->
Spec
specArgumentPatternMatching (ArgumentPatternMatchingDeps { _getValueBy }) = do
it "Multi-case stubbing correctly dispatches and collects results for distinct arguments" do
result <- runMockT do
_ <- _getValueBy $ do
onCase $ "a" ~> pure @IO "ax"
onCase $ "b" ~> pure @IO "bx"
onCase $ "c" ~> pure @IO "cx"
getValues ["a", "b", "c"]
result `shouldBe` ["ax", "bx", "cx"]
specMonadStateTransformerSupport ::
( MonadStateSub String (MockT (StateT String IO))
, MonadStateSub2 String (MockT (StateT String IO))
) =>
MonadStateTransformerDeps ->
Spec
specMonadStateTransformerSupport (MonadStateTransformerDeps { _fnState, _fnState2 }) = do
it "Mock with StateT correctly consumes input and returns next value" do
let action = runMockT $ do
_ <- _fnState $ do
onCase $ Just "current" ~> pure @(StateT String IO) "next"
onCase $ Nothing ~> pure @(StateT String IO) "default"
fnState (Just "current")
result <- evalStateT action "seed"
result `shouldBe` "next"
it "Mock in StateT correctly executes and leaves the state unchanged (unit return)" do
let action = runMockT $ do
_ <- _fnState2 $ "label" ~> pure @(StateT String IO) ()
fnState2 @String "label"
result <- evalStateT action "initial"
result `shouldBe` ()
specMultiParamTypeClassArity ::
( MonadVar2_1Sub (MockT IO) String
, MonadVar2_2Sub String (MockT IO)
, MonadVar3_1Sub (MockT IO) String String
, MonadVar3_2Sub String (MockT IO) String
, MonadVar3_3Sub String String (MockT IO)
) =>
MultiParamTypeClassArityDeps ->
Spec
specMultiParamTypeClassArity (MultiParamTypeClassArityDeps { _fn2_1Sub, _fn2_2Sub, _fn3_1Sub, _fn3_2Sub, _fn3_3Sub }) = do
it "Type variable MonadVar2_1Sub is correctly resolved and mocked" do
result <- runMockT do
_ <- _fn2_1Sub $ "alpha" ~> pure @IO ()
fn2_1Sub @(MockT IO) @String "alpha"
result `shouldBe` ()
it "Type variable MonadVar2_2Sub is correctly resolved and mocked" do
result <- runMockT do
_ <- _fn2_2Sub $ "beta" ~> pure @IO ()
fn2_2Sub @String @(MockT IO) "beta"
result `shouldBe` ()
it "Type variable MonadVar3_1Sub is correctly resolved and mocked" do
result <- runMockT do
_ <- _fn3_1Sub $ "gamma" ~> pure @IO ()
fn3_1Sub @(MockT IO) @String @String "gamma"
result `shouldBe` ()
it "Type variable MonadVar3_2Sub is correctly resolved and mocked" do
result <- runMockT do
_ <- _fn3_2Sub $ "delta" ~> pure @IO ()
fn3_2Sub @String @(MockT IO) @String "delta"
result `shouldBe` ()
it "Type variable MonadVar3_3Sub is correctly resolved and mocked" do
result <- runMockT do
_ <- _fn3_3Sub $ "epsilon" ~> pure @IO ()
fn3_3Sub @String @String @(MockT IO) "epsilon"
result `shouldBe` ()
specFunctionalDependenciesSupport ::
( ParamThreeMonad Int Bool (MockT IO)
) =>
FunctionalDependenciesDeps ->
Spec
specFunctionalDependenciesSupport (FunctionalDependenciesDeps { _fnParam3_1, _fnParam3_2, _fnParam3_3 }) = do
it "FunDeps are correctly resolved allowing multiple actions to return values" do
result <- runMockT $ do
_ <- _fnParam3_1 $ do
onCase $ (1 :: Int) ~> True ~> pure @IO "combined"
_ <- _fnParam3_2 $ casesIO [1 :: Int]
_ <- _fnParam3_3 $ casesIO [True]
r1 <- fnParam3_1 (1 :: Int) True
r2 <- fnParam3_2
r3 <- fnParam3_3
pure (r1, r2, r3)
result `shouldBe` ("combined", 1, True)
specExplicitMonadicReturnValues ::
( ExplicitlyReturnMonadicValuesTest (MockT IO)
) =>
ExplicitMonadicReturnDeps ->
Spec
specExplicitMonadicReturnValues (ExplicitMonadicReturnDeps { _getByExplicit, _echoExplicit }) = do
it "Explicitly stubbed function returns value and subsequent action is verified" do
result <- runMockT do
_ <- _getByExplicit $ "key" ~> pure @IO (42 :: Int)
_ <- _echoExplicit $ "value" ~> pure @IO ()
v <- getByExplicit "key"
echoExplicit "value"
pure v
result `shouldBe` 42
it "Helper program correctly uses and echoes the explicitly stubbed monadic return value" do
result <- runMockT do
_ <- _getByExplicit $ "s" ~> pure @IO (10 :: Int)
_ <- _echoExplicit $ "10" ~> pure @IO ()
echoProgramExplicit "s"
result `shouldBe` ()
specDefaultMethodMocking ::
( DefaultMethodTest (MockT IO)
) =>
DefaultMethodDeps ->
Spec
specDefaultMethodMocking (DefaultMethodDeps { _defaultAction }) = do
it "Default method is successfully overridden and stubbed value is returned" do
result <- runMockT do
_ <- _defaultAction (Head :> param (99 :: Int))
defaultAction
result `shouldBe` 99
specAssociatedTypeFamiliesSupport ::
( AssocTypeTest (MockT IO)
, ResultType (MockT IO) ~ Int
) =>
AssociatedTypeFamiliesDeps ->
Spec
specAssociatedTypeFamiliesSupport (AssociatedTypeFamiliesDeps { _produce }) = do
it "Associated Type family is correctly resolved and mocked stub value is returned" do
v <- runMockT do
_ <- _produce (Head :> param (321 :: Int))
produce
v `shouldBe` 321
specConcurrencyAndUnliftIO ::
( MonadAsync (MockT IO)
, FileOperation (MockT IO)
) =>
ConcurrencyAndUnliftIODeps ->
Spec
specConcurrencyAndUnliftIO (ConcurrencyAndUnliftIODeps { _readFile }) = do
it "Concurrent execution (mapConcurrently) correctly calls and collects results from mocks" do
result <- runMockT do
_ <- _readFile $ do
onCase $ "file1.txt" ~> pack "content1"
onCase $ "file2.txt" ~> pack "content2"
processFiles ["file1.txt", "file2.txt"]
result `shouldBe` [pack "content1", pack "content2"]
it "MonadUnliftIO correctly handles internal async operation and verification" do
result <- runMockT do
_ <- _readFile $ do
onCase $ "test.txt" ~> pack "content"
content <- withRunInIO $ \runInIO -> do
asyncAction <- async $ runInIO (readFile "test.txt")
wait asyncAction
liftIO $ content `shouldBe` pack "content"
pure content
result `shouldBe` pack "content"
it "MonadUnliftIO basic runInIO functionality is verified correctly" do
result <- runMockT do
_ <- _readFile $ do
onCase $ "test.txt" ~> pack "test content"
content <- withRunInIO $ \runInIO -> do
runInIO (readFile "test.txt")
liftIO $ content `shouldBe` pack "test content"
pure content
result `shouldBe` pack "test content"
specUserDefinedTypeSupport ::
( UserDefinedClass (MockT IO)
) =>
UserDefinedTypeDeps ->
Spec
specUserDefinedTypeSupport (UserDefinedTypeDeps { _processPost }) = do
it "User defined class method handles shared user-defined type correctly" do
let p = Post 1 "title"
runMockT @IO $ do
_ <- _processPost $ do
-- verify that we can match exactly on the user defined type
onCase $ p ~> True
result <- processPost p
liftIO $ result `shouldBe` True
-- Verification Failure Tests
specBasicVerificationFailureDetection ::
( FileOperation (MockT IO)
) =>
BasicDeps ->
Spec
specBasicVerificationFailureDetection (BasicDeps { _readFile, _writeFile }) = describe "verification failures (FileOperation)" do
it "Error when read stub is defined but target function readFile is never called" do
(runMockT @IO do
_ <- _readFile ("input.txt" ~> pack "content")
`expects` do
called once
-- readFile is never called
pure ()) `shouldThrow` missingCall "readFile"
it "Error when write stub is defined but target function writeFile is never called" do
(runMockT @IO do
_ <- _writeFile ("output.txt" ~> pack "content" ~> ())
`expects` do
called once
-- writeFile is never called
pure ()) `shouldThrow` missingCall "writeFile"
it "Error when read stub expects call but only writeFile is executed" do
(runMockT @IO do
_ <- _readFile ("input.txt" ~> pack "content")
`expects` do
called once
_ <- _writeFile ("output.txt" ~> pack "content" ~> ())
-- readFile is never called, only writeFile is called
do
writeFile "output.txt" (pack "content")
pure ()) `shouldThrow` missingCall "readFile"
it "Error when write stub expects call but only readFile is executed" do
(runMockT @IO do
_ <- _readFile ("input.txt" ~> pack "content")
_ <- _writeFile ("output.txt" ~> pack "content" ~> ())
`expects` do
called once
-- writeFile is never called, only readFile is called
do
readFile "input.txt") `shouldThrow` missingCall "writeFile"
specMonadReaderVerificationFailureDetection ::
ReaderContextDeps ->
Spec
specMonadReaderVerificationFailureDetection (ReaderContextDeps { _ask }) = describe "verification failures (Reader Environment)" do
it "Error when MonadReader stub _ask is defined but target function ask is never called" do
(runMockT @IO do
_ <- _ask (Head :> param (Environment "input.txt" "output.txt"))
`expects` do
called once
-- ask is never called
pure ()) `shouldThrow` missingCall "ask"
specImplicitReturnVerificationFailureDetection ::
ImplicitMonadicReturnDeps ->
Spec
specImplicitReturnVerificationFailureDetection (ImplicitMonadicReturnDeps { _getBy, _echo }) = describe "verification failures (TestClass)" do
it "Error when _getBy stub expects call but getBy is never executed" do
(runMockT @IO do
_ <- _getBy ("s" ~> pure @IO (10 :: Int))
`expects` do
called once
pure ()) `shouldThrow` missingCall "_getBy"
it "Error when _echo stub expects call but echo is never executed" do
(runMockT @IO do
_ <- _echo ("10" ~> pure @IO ())
`expects` do
called once
pure ()) `shouldThrow` missingCall "_echo"
specMultiParamVerificationFailureDetection ::
MultiParamTypeClassArityDeps ->
Spec
specMultiParamVerificationFailureDetection (MultiParamTypeClassArityDeps { _fn2_1Sub, _fn2_2Sub, _fn3_1Sub, _fn3_2Sub, _fn3_3Sub }) = describe "verification failures (SubVars)" do
it "Error when _fn2_1Sub stub expects call but fn2_1Sub is never executed" do
(runMockT @IO do
_ <- _fn2_1Sub ("alpha" ~> pure @IO ())
`expects` do
called once
pure ()) `shouldThrow` missingCall "_fn2_1Sub"
it "Error when _fn2_2Sub stub expects call but fn2_2Sub is never executed" do
(runMockT @IO do
_ <- _fn2_2Sub ("beta" ~> pure @IO ())
`expects` do
called once
pure ()) `shouldThrow` missingCall "_fn2_2Sub"
it "Error when _fn3_1Sub stub expects call but fn3_1Sub is never executed" do
(runMockT @IO do
_ <- _fn3_1Sub ("gamma" ~> pure @IO ())
`expects` do
called once
pure ()) `shouldThrow` missingCall "_fn3_1Sub"
it "Error when _fn3_2Sub stub expects call but fn3_2Sub is never executed" do
(runMockT @IO do
_ <- _fn3_2Sub ("delta" ~> pure @IO ())
`expects` do
called once
pure ()) `shouldThrow` missingCall "_fn3_2Sub"
it "Error when _fn3_3Sub stub expects call but fn3_3Sub is never executed" do
(runMockT @IO do
_ <- _fn3_3Sub ("epsilon" ~> pure @IO ())
`expects` do
called once
pure ()) `shouldThrow` missingCall "_fn3_3Sub"
specArgumentMatchingVerificationFailureDetection ::
ArgumentPatternMatchingDeps ->
Spec
specArgumentMatchingVerificationFailureDetection (ArgumentPatternMatchingDeps { _getValueBy }) = describe "verification failures (MultiApply)" do
it "Error when multi-case stub _getValueBy expects call but getValueBy is never executed" do
(runMockT @IO do
_ <- _getValueBy (do onCase $ "a" ~> pure @IO "ax")
`expects` do
called once
pure ()) `shouldThrow` missingCall "_getValueBy"
specFunDepsVerificationFailureDetection ::
FunctionalDependenciesDeps ->
Spec
specFunDepsVerificationFailureDetection (FunctionalDependenciesDeps { _fnParam3_1, _fnParam3_2, _fnParam3_3 }) = describe "verification failures (ParamThreeMonad)" do
it "Error when FunDep stub _fnParam3_1 expects call but fnParam3_1 is never executed" do
(runMockT @IO do
_ <- _fnParam3_1 (do
onCase $ (1 :: Int) ~> True ~> pure @IO "combined")
`expects` do
called once
pure ()) `shouldThrow` missingCall "_fnParam3_1"
it "Error when FunDep stub _fnParam3_2 expects call but fnParam3_2 is never executed" do
(runMockT @IO do
_ <- _fnParam3_2 (casesIO [1 :: Int])
`expects` do
called once
pure ()) `shouldThrow` missingCall "_fnParam3_2"
it "Error when FunDep stub _fnParam3_3 expects call but fnParam3_3 is never executed" do
(runMockT @IO do
_ <- _fnParam3_3 (casesIO [True])
`expects` do
called once
pure ()) `shouldThrow` missingCall "_fnParam3_3"
specExplicitReturnVerificationFailureDetection ::
ExplicitMonadicReturnDeps ->
Spec
specExplicitReturnVerificationFailureDetection (ExplicitMonadicReturnDeps { _getByExplicit, _echoExplicit }) = describe "verification failures (ExplicitReturn)" do
it "Error when explicit stub _getByExplicit expects call but getByExplicit is never executed" do
(runMockT @IO do
_ <- _getByExplicit ("key" ~> pure @IO (42 :: Int))
`expects` do
called once
pure ()) `shouldThrow` missingCall "_getByExplicit"
it "Error when explicit stub _echoExplicit expects call but echoExplicit is never executed" do
(runMockT @IO do
_ <- _echoExplicit ("value" ~> pure @IO ())
`expects` do
called once
pure ()) `shouldThrow` missingCall "_echoExplicit"
specAdvancedTypesVerificationFailureDetection ::
DefaultMethodDeps ->
AssociatedTypeFamiliesDeps ->
Spec
specAdvancedTypesVerificationFailureDetection (DefaultMethodDeps { _defaultAction }) (AssociatedTypeFamiliesDeps { _produce }) = describe "verification failures (Default/Assoc)" do
it "Error when default method stub _defaultAction expects call but defaultAction is never executed" do
(runMockT @IO do
_ <- _defaultAction (Head :> param (99 :: Int))
`expects` do
called once
pure ()) `shouldThrow` missingCall "_defaultAction"
it "Error when associated type stub _produce expects call but produce is never executed" do
(runMockT @IO do
_ <- _produce (Head :> param (321 :: Int))
`expects` do
called once
pure ()) `shouldThrow` missingCall "_produce"
specSequentialStubbingVerificationFailureDetection ::
SequentialIODeps ->
Spec
specSequentialStubbingVerificationFailureDetection (SequentialIODeps { _readTTY, _writeTTY }) = describe "verification failures (TTY)" do
it "Error when sequential stub _readTTY expects call but readTTY is never executed" do
(runMockT @IO do
_ <- _readTTY (casesIO ["a", ""])
`expects` do
called once
pure ()) `shouldThrow` missingCall "_readTTY"
it "Error when sequential stub _writeTTY expects call but writeTTY is never executed" do
(runMockT @IO do
_ <- _writeTTY ("a" ~> pure @IO ())
`expects` do
called once
pure ()) `shouldThrow` missingCall "_writeTTY"