packages feed

mockcat-1.3.0.0: test/Test/MockCat/DeferredTypeErrorsSpec.hs

{-# OPTIONS_GHC -fdefer-type-errors -Wno-deferred-type-errors #-}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_GHC -Wno-unused-do-bind #-}
{-# LANGUAGE DataKinds #-}

module Test.MockCat.DeferredTypeErrorsSpec (spec) where

import Test.Hspec
import Test.MockCat
import Control.Exception (evaluate)
import Control.Monad.IO.Class (liftIO)
import Prelude hiding (any)

spec :: Spec
spec = describe "Compile-time restrictions (Deferred Type Errors)" do
  it "expects throws type error when applied directly to MockSpec instead of mock result" do
    -- This expression should fail to typecheck because `expects` requires `m fn` (monadic action),
    -- but here it is applied to `MockSpec` (pure value).
    -- With -fdefer-type-errors, this becomes a runtime error.
    let expression = (any ~> True) `expects` do
          called once
    
    evaluate expression `shouldThrow` anyException

  it "expects throws type error when applied to instantiated mock function (f)" do
    -- f <- mock ... returns a function `f`.
    -- expects expects `m fn`, not `fn`.
    -- This verifies that we cannot "attach" expectations to an existing function variable.
    -- We type-annotate the block to ensure 'mock' has a concreter context (MockT IO),
    -- so that the error is specifically about 'f' not matching 'm fn'.
    let expression :: MockT IO ()
        expression = do
          f <- mock (any ~> (1 :: Int))
          let val = f `expects` do
                called once
          liftIO $ evaluate val
          pure ()
    
    -- Since the type error is deferred inside the IO action logic, we must run it to trigger the exception.
    runMockT expression `shouldThrow` anyException