{-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ConstraintKinds #-} module Test.Hspec.Contrib.Mocks.V1 ( stubAction , withSpy ) where import Test.HUnit import Data.CallStack (HasCallStack) import Data.IORef #if !MIN_VERSION_base(4,6,0) atomicModifyIORef' :: IORef a -> (a -> (a, b)) -> IO b atomicModifyIORef' = atomicModifyIORef #endif -- | Create a [test stub](https://en.wikipedia.org/wiki/Test_stub) action. -- -- >>> stub <- stubAction ["foo", "bar", "baz"] -- >>> stub -- "foo" -- >>> stub -- "bar" -- >>> stub -- "baz" -- >>> stub -- *** Exception: HUnitFailure ...stubAction: no values left... -- -- @since 0.5.2 stubAction :: HasCallStack => [a] -> IO (IO a) stubAction values = do ref <- newIORef values return $ do atomicModifyIORef ref takeValue >>= maybe noValuesLeft return where noValuesLeft :: IO a noValuesLeft = assertFailure "stubAction: no values left" takeValue :: [a] -> ([a], Maybe a) takeValue xs = case xs of [] -> ([], Nothing) a : as -> (as, Just a) -- | Create a [test spy](https://en.wikipedia.org/wiki/Test_double) action. -- -- Record any arguments that are passed to that action. -- -- >>> withSpy $ \ spy -> spy "foo" >> spy "bar" >> spy "baz" -- ["foo","bar","baz"] -- -- @since 0.5.2 withSpy :: ((a -> IO ()) -> IO ()) -> IO [a] withSpy action = do ref <- newIORef [] action (\ x -> atomicModifyIORef' ref $ \ xs -> (x : xs, ())) reverse `fmap` readIORef ref