packages feed

mockcat-1.3.3.0: test/Test/MockCat/WithMockIOSpec.hs

{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeApplications #-}
module Test.MockCat.WithMockIOSpec (spec) where

import Test.Hspec
import Test.MockCat
import Control.Exception (try, ErrorCall(..), SomeException)
import Control.Concurrent (forkIO)
import Control.Concurrent.MVar (newEmptyMVar, putMVar, takeMVar)
import Control.Monad (void)

spec :: Spec
spec = do
  describe "withMockIO" $ do
    it "can run IO actions directly without liftIO" $ do
      withMockIO $ do
        f <- mock $ "hello" ~> "world"
        f "hello" `shouldBe` "world"
        f `shouldBeCalled` "hello"

    it "cleans up context even if an exception occurs" $ do
      -- Raise an exception in the first test
      void $ try @ErrorCall $ withMockIO $ do
        void $ mock $ "a" ~> "b"
        error "force fail"

      -- Verify that no remnants (like expectations) from the previous test remain in the second test
      withMockIO $ do
        f <- mock $ "x" ~> "y"
        f "x" `shouldBe` "y"
        -- Verify that the expectation "a" ~> "b" from the previous test is not verified here

    it "supports nested withMockIO" $ do
      withMockIO $ do
        f1 <- mock $ "outer" ~> "ok"
        withMockIO $ do
          f2 <- mock $ "inner" ~> "ok"
          f2 "inner" `shouldBe` "ok"
          f2 `shouldBeCalled` "inner"
        f1 "outer" `shouldBe` "ok"
        f1 `shouldBeCalled` "outer"

    it "isolates context between parent and child threads" $ do
      mvar <- newEmptyMVar
      withMockIO $ do
        f <- mock $ "parent" ~> "ok"
        void $ forkIO $ do
          res <- try @SomeException (mock ("child" ~> "ok") `expects` called once)
          putMVar mvar res

        f "parent" `shouldBe` "ok"
        f `shouldBeCalled` "parent"

      childRes <- takeMVar mvar
      case childRes of
        Left e -> show e `shouldContain` "No WithMockContext found"
        Right _ -> fail "Child thread should not have accessed parent context"