diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2014-2022 Simon Hengel <sol@typeful.net>
+Copyright (c) 2014-2023 Simon Hengel <sol@typeful.net>
 Copyright (c) 2014-2014 Junji Hashimoto <junji.hashimoto@gree.net>
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
diff --git a/hspec-contrib.cabal b/hspec-contrib.cabal
--- a/hspec-contrib.cabal
+++ b/hspec-contrib.cabal
@@ -1,21 +1,21 @@
 cabal-version: 1.12
 
--- This file has been generated from package.yaml by hpack version 0.35.0.
+-- This file has been generated from package.yaml by hpack version 0.35.1.
 --
 -- see: https://github.com/sol/hpack
 
 name:           hspec-contrib
-version:        0.5.1.1
+version:        0.5.2
 license:        MIT
 license-file:   LICENSE
-copyright:      (c) 2011-2022 Simon Hengel,
+copyright:      (c) 2011-2023 Simon Hengel,
                 (c) 2014 Junji Hashimoto
 maintainer:     Simon Hengel <sol@typeful.net>
 build-type:     Simple
 category:       Testing
 stability:      experimental
 bug-reports:    https://github.com/hspec/hspec/issues
-homepage:       http://hspec.github.io/
+homepage:       https://hspec.github.io/
 synopsis:       Contributed functionality for Hspec
 description:    Contributed functionality for Hspec
 
@@ -31,9 +31,11 @@
   build-depends:
       HUnit
     , base ==4.*
+    , call-stack
     , hspec-core >=2.5.0
   exposed-modules:
       Test.Hspec.Contrib.HUnit
+      Test.Hspec.Contrib.Mocks.V1
       Test.Hspec.Contrib.Retry
   other-modules:
       Paths_hspec_contrib
@@ -47,6 +49,7 @@
   other-modules:
       Helper
       Test.Hspec.Contrib.HUnitSpec
+      Test.Hspec.Contrib.Mocks.V1Spec
       Test.Hspec.Contrib.RetrySpec
       Paths_hspec_contrib
   ghc-options: -Wall
@@ -56,6 +59,7 @@
       HUnit
     , QuickCheck
     , base ==4.*
+    , call-stack
     , hspec
     , hspec-contrib
     , hspec-core >=2.6.0
diff --git a/src/Test/Hspec/Contrib/Mocks/V1.hs b/src/Test/Hspec/Contrib/Mocks/V1.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/Hspec/Contrib/Mocks/V1.hs
@@ -0,0 +1,57 @@
+{-# 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
diff --git a/test/Test/Hspec/Contrib/Mocks/V1Spec.hs b/test/Test/Hspec/Contrib/Mocks/V1Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Hspec/Contrib/Mocks/V1Spec.hs
@@ -0,0 +1,27 @@
+module Test.Hspec.Contrib.Mocks.V1Spec (spec) where
+
+import           Test.Hspec
+import           Test.HUnit.Lang
+
+import           Test.Hspec.Contrib.Mocks.V1
+
+hUnitFailure :: FailureReason -> HUnitFailure -> Bool
+hUnitFailure expected (HUnitFailure _ actual) = actual == expected
+
+spec :: Spec
+spec = do
+  describe "stubAction" $ do
+    it "creates a stub action" $ do
+      stub <- stubAction [23, 42, 65 :: Int]
+      stub `shouldReturn` 23
+      stub `shouldReturn` 42
+      stub `shouldReturn` 65
+      stub `shouldThrow` hUnitFailure (Reason "stubAction: no values left")
+
+  describe "withSpy" $ do
+    it "records arguments" $ do
+      withSpy $ \ spy -> do
+        spy "foo"
+        spy "bar"
+        spy "baz"
+      `shouldReturn` ["foo", "bar", "baz"]
