mockcat-1.3.0.0: test/Test/MockCat/HPCNestSpec.hs
{-# LANGUAGE BlockArguments #-}
{-# OPTIONS_GHC -Wno-unused-do-bind #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
module Test.MockCat.HPCNestSpec (spec) where
import Test.Hspec
import Test.MockCat
import Control.Exception (try, SomeException, evaluate)
import Control.Monad.IO.Class (liftIO)
import Data.Either (isLeft)
spec :: Spec
spec = describe "HPC Nesting and Isolation" $ do
it "RunMockT nested in IO: outer mock with 'shouldBeCalled' becomes unverifiable inside runMockT" $ do
outer <- mock (param "outer" ~> True)
-- execution in IO context
evaluate (outer "outer")
runMockT @IO $ do
-- runMockT resets history, so 'outer' should not be verifiable here.
result <- liftIO $ try @SomeException (outer `shouldBeCalled` "outer")
liftIO $ result `shouldSatisfy` isLeft
it "RunMockT nested in IO: outer mock is verifiable AFTER runMockT returns" $ do
outer <- mock (param "outer" ~> True)
evaluate (outer "outer")
runMockT @IO $ do
pure ()
-- Post-runMockT, history is reset. Verification should fail.
result <- try @SomeException (outer `shouldBeCalled` "outer")
result `shouldSatisfy` isLeft
it "withMock (using expects) is immune to history reset" $ do
withMock $ do
f <- mock (param "a" ~> True) `expects` do
called once `with` "a"
liftIO $ do
evaluate (f "a")
-- Nested runMockT clears history, but expectations capture the recorder directly.
runMockT @IO $ pure ()
pure ()
it "Sibling runMockT blocks are isolated" $ do
runMockT @IO $ do
f1 <- mock (param "a" ~> True)
liftIO $ evaluate (f1 "a")
liftIO $ f1 `shouldBeCalled` "a"
runMockT @IO $ do
f2 <- mock (param "a" ~> True)
liftIO $ evaluate (f2 "a")
liftIO $ f2 `shouldBeCalled` "a"
it "Nested withMock blocks work correctly even with same-type mocks" $ do
withMock $ do
-- Outer mock
outer <- mock (param "a" ~> True) `expects` do
called once
liftIO $ do
evaluate (outer "a")
-- Inner withMock
withMock $ do
-- Inner mock with SAME signature
inner <- mock (param "a" ~> True) `expects` do
called once
liftIO $ evaluate (inner "a")
pure ()
pure ()
it "Nested runMockT: inner block clears outer block's history" $ do
runMockT @IO $ do
outer <- mock (param "a" ~> True)
liftIO $ evaluate (outer "a")
-- At this point, outer is verifiable
liftIO $ outer `shouldBeCalled` "a"
liftIO $ runMockT @IO $ do
-- Inner block resets history.
-- 'outer' (defined in parent runMockT) relies on history lookup (HPC).
-- So verifies should fail here.
result <- liftIO $ try @SomeException (outer `shouldBeCalled` "a")
liftIO $ result `shouldSatisfy` isLeft
-- Inner mock should work checking
inner <- mock (param "b" ~> True)
liftIO $ evaluate (inner "b")
liftIO $ inner `shouldBeCalled` "b"
-- After inner block returns, history is NOT restored (resetMockHistory is destructive).
-- So outer mock remains broken.
result <- liftIO $ try @SomeException (outer `shouldBeCalled` "a")
liftIO $ result `shouldSatisfy` isLeft
it "withMock inside runMockT works correctly" $ do
runMockT @IO $ do
f <- mock (param "a" ~> True)
liftIO $ evaluate (f "a")
liftIO $ withMock $ do
g <- mock (param "b" ~> True)
pure g `expects` do called once
liftIO $ evaluate (g "b")
pure ()
-- withMock does not reset history, so 'f' should still be verifiable
liftIO $ f `shouldBeCalled` "a"