packages feed

mockcat-1.3.2.0: test/Test/MockCat/DeriveSpec.hs

{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -Wno-redundant-constraints #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# OPTIONS_GHC -Wno-unused-do-bind #-}
{-# OPTIONS_GHC -Wno-orphans #-}
{- HLINT ignore "Use newtype instead of data" -}
module Test.MockCat.DeriveSpec (spec) where

import Test.Hspec
import Test.MockCat
import Control.Monad (void)
import Control.Monad.Error.Class (MonadError(..))
import Control.Monad.Reader (MonadReader(..), ReaderT(..), runReaderT)
import Control.Monad.Except (ExceptT(..), runExceptT)
import Control.Monad.IO.Class (MonadIO(..))

-- Target class for deriveMockInstances
class Monad m => Logger m where
  logInfo :: String -> m ()

instance Logger IO where
  logInfo _ = pure ()

deriveMockInstances [t|Logger|]

-- Complex patterns for deriveMockInstances
class Monad m => ComplexLogger m where
  logMany :: String -> [Int] -> m ()
  logIf :: Bool -> String -> m ()

instance ComplexLogger IO where
  logMany _ _ = pure ()
  logIf _ _ = pure ()

deriveMockInstances [t|ComplexLogger|]

-- Target class for deriveNoopInstance
class Monad m => Auditor m where
  audit :: String -> m ()

deriveNoopInstance [t|Auditor|]

-- Custom error for testing
data CustomError = CustomError String deriving (Eq, Show)

-- Direct MonadError mock test
makeAutoLiftMock [t|MonadError CustomError|]

-- Isolated monad to avoid MonadError IOException IO conflict
newtype TestM a = TestM { runTestM :: IO a } deriving (Functor, Applicative, Monad, MonadIO)

spec :: Spec
spec = do
  describe "MTL instances" $ do
    it "can use catchError directly in MockT" $ do
      let action = runMockT $ do
            let action' = throwError "error" :: MockT (ExceptT String IO) Int
            action' `catchError` (\e -> if e == "error" then pure 42 else throwError e)
      runExceptT action `shouldReturn` Right (42 :: Int)

    it "can use ask directly in MockT" $ do
      let action = runMockT (ask :: MockT (ReaderT String IO) String)
      runReaderT action "env" `shouldReturn` "env"

  describe "deriveMockInstances" $ do
    it "can derive basic custom typeclass instances" $ do
      runMockT (logInfo "hello" :: MockT IO ()) `shouldReturn` ()

    it "can derive complex custom typeclass instances (multi-args, list)" $ do
      runMockT (do
        logMany "counts" [1, 2, 3]
        logIf True "active"
        pure ()) `shouldReturn` ()

  describe "deriveNoopInstance" $ do
    it "doesn't require stubs for noop methods" $ do
      runMockT (audit "something" :: MockT IO ()) `shouldReturn` ()

  describe "makeMock/makeAutoLiftMock with MonadError" $ do
    it "can mock standard MonadError directly using isolated TestM" $ do
      let action :: MockT TestM ()
          action = do
            _throwError (CustomError "fail" ~> (pure () :: MockT TestM ()))
            void (throwError (CustomError "fail") :: MockT TestM ())
      runTestM (runMockT action) `shouldReturn` ()