diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
 # Revision history for hmock
 
+## 0.5.2.0 -- 2026-09-01
+
+* Supported GHC versions are now 9.0 through 9.14.
+  * Support for GHC 8.6, 8.8, and 8.10 has been dropped.
+* Version bounds now allow GHC 9.10, 9.12, and 9.14.
+* Bumped dependency version upper bounds.
+* Removed unused dependencies on `constraints` and `monad-control`.
+
 ## 0.5.1.2 -- 2023-11-28
 
 * Version bounds now allow GHC 9.6 and 9.8
diff --git a/HMock.cabal b/HMock.cabal
--- a/HMock.cabal
+++ b/HMock.cabal
@@ -1,6 +1,6 @@
 cabal-version:      2.4
 name:               HMock
-version:            0.5.1.2
+version:            0.5.2.0
 synopsis:           A flexible mock framework for testing effectful code.
 description:        HMock is a flexible mock framework for testing effectful
                     code in Haskell.  Tests can set up expectations about
@@ -19,13 +19,20 @@
 author:             Chris Smith <cdsmith@gmail.com>
 maintainer:         Chris Smith <cdsmith@gmail.com>
 
-extra-source-files: CHANGELOG.md, README.md
+extra-doc-files:    CHANGELOG.md, README.md
 
-tested-with:        GHC == 8.6.5 || == 8.8.4 || == 8.10.7 || == 9.0.2 || == 9.2.8 || == 9.4.5 || == 9.6.2 || == 9.8.1
+tested-with:        GHC == 9.0.2
+                     || == 9.2.8
+                     || == 9.4.8
+                     || == 9.6.7
+                     || == 9.8.4
+                     || == 9.10.3
+                     || == 9.12.4
+                     || == 9.14.1
 
 source-repository head
     type:     git
-    location: git://github.com/cdsmith/HMock.git
+    location: https://github.com/cdsmith/HMock.git
 
 library
     exposed-modules:  Test.HMock,
@@ -42,18 +49,16 @@
                       Test.HMock.Internal.Step,
                       Test.HMock.Internal.TH,
                       Test.HMock.Internal.Util
-    build-depends:    base >=4.11.0 && < 4.20,
-                      constraints >= 0.13 && < 0.15,
-                      containers >= 0.6.2 && < 0.8,
-                      data-default >= 0.7.1 && < 0.8,
+    build-depends:    base >= 4.15.0 && < 4.23,
+                      containers >= 0.6.2 && < 0.9,
+                      data-default >= 0.7.1 && < 0.9,
                       exceptions >= 0.10.4 && < 0.11,
                       explainable-predicates >= 0.1 && < 0.2,
-                      extra >= 1.7.9 && < 1.8,
-                      monad-control >= 1.0.2 && < 1.1,
+                      extra >= 1.7.9 && < 1.9,
                       mtl >= 2.2.2 && < 2.4,
                       stm >= 2.5.0 && < 2.6,
                       syb >= 0.7.2 && < 0.8,
-                      template-haskell >= 2.14 && < 2.22,
+                      template-haskell >= 2.17 && < 2.25,
                       transformers-base >= 0.4.5 && < 0.5,
                       unliftio >= 0.2.18 && < 0.3,
     hs-source-dirs:   src
@@ -77,10 +82,8 @@
     build-depends:    HMock,
                       QuickCheck,
                       base,
-                      containers,
                       data-default,
                       deepseq,
-                      directory,
                       doctest-exitcode-stdio,
                       doctest-lib,
                       exceptions,
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -620,7 +620,7 @@
 
 ### Which GHC versions are supported?
 
-HMock is tested with GHC versions from 8.6 through 9.4.
+HMock is tested with GHC versions from 9.0 through 9.14.
 
 ## Case Study: Mocking Template Haskell
 
diff --git a/src/Test/HMock/Internal/State.hs b/src/Test/HMock/Internal/State.hs
--- a/src/Test/HMock/Internal/State.hs
+++ b/src/Test/HMock/Internal/State.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE CPP #-}
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE GADTs #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
@@ -36,7 +35,7 @@
 import Test.HMock.Mockable (Mockable (..))
 import UnliftIO
   ( MonadIO,
-    MonadUnliftIO(withRunInIO),
+    MonadUnliftIO,
     STM,
     TVar,
     atomically,
@@ -47,10 +46,6 @@
   )
 import Data.Kind (Type, Constraint)
 
-#if !MIN_VERSION_base(4, 13, 0)
-import Control.Monad.Fail (MonadFail)
-#endif
-
 -- | The severity for a possible problem.
 data Severity
   = -- | Fail the test.
@@ -198,16 +193,12 @@
       MonadBase b,
       MonadCatch,
       MonadMask,
-      MonadThrow
+      MonadThrow,
+      MonadUnliftIO
     )
 
 instance MonadTrans MockT where
   lift = MockT . lift
-
--- Note: The 'MonadUnliftIO' instance is implemented manually because deriving
--- it causes compilation failure in GHC 8.6 and 8.8.  (See issue #23.)
-instance MonadUnliftIO m => MonadUnliftIO (MockT m) where
-  withRunInIO inner = MockT $ withRunInIO $ \run -> inner (run . unMockT)
 
 -- | Applies a function to the base monad of 'MockT'.
 mapMockT :: (m a -> m b) -> MockT m a -> MockT m b
diff --git a/src/Test/HMock/Internal/TH.hs b/src/Test/HMock/Internal/TH.hs
--- a/src/Test/HMock/Internal/TH.hs
+++ b/src/Test/HMock/Internal/TH.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE CPP #-}
 {-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE TupleSections #-}
 
@@ -32,8 +31,6 @@
 import Language.Haskell.TH.Syntax (NameFlavour (..))
 import Test.HMock.Internal.Util (choices)
 
-#if MIN_VERSION_template_haskell(2,17,0)
-
 -- | Fetches the 'Name' of a 'TyVarBndr'.
 tvName :: TyVarBndr flag -> Name
 tvName (PlainTV name _) = name
@@ -42,19 +39,6 @@
 -- | Creates a 'TyVarBndr' for a plain variable without a kind annotation.
 bindVar :: Name -> TyVarBndr Specificity
 bindVar n = PlainTV n SpecifiedSpec
-
-#else
-
--- | Fetches the 'Name' of a 'TyVarBndr'.
-tvName :: TyVarBndr -> Name
-tvName (PlainTV name) = name
-tvName (KindedTV name _) = name
-
--- | Creates a 'TyVarBndr' for a plain variable without a kind annotation.
-bindVar :: Name -> TyVarBndr
-bindVar = PlainTV
-
-#endif
 
 -- | Gets the unapplied top-level name from a type application.
 unappliedName :: Type -> Maybe Name
diff --git a/src/Test/HMock/MockMethod.hs b/src/Test/HMock/MockMethod.hs
--- a/src/Test/HMock/MockMethod.hs
+++ b/src/Test/HMock/MockMethod.hs
@@ -71,7 +71,8 @@
 mockMethodImpl surrogate action = join $
   fromMockSetup $ do
     initClassIfNeeded (Proxy :: Proxy cls)
-    states <- allStates <$> MockSetup ask
+    currentState <- MockSetup ask
+    let states = allStates currentState
     (partial, full) <- fmap (bimap concat concat . unzip) $
       forM states $ \state -> do
         expectSet <- mockSetupSTM $ readTVar (mockExpectSet state)
@@ -87,10 +88,10 @@
     sideEffect <-
       getSideEffect
         <$> concatMapM (mockSetupSTM . readTVar . mockSideEffects) states
-    ambigSev <- mockSetupSTM $ readTVar . mockAmbiguitySeverity . head $ states
+    ambigSev <- mockSetupSTM $ readTVar (mockAmbiguitySeverity currentState)
     unintSev <-
-      mockSetupSTM $ readTVar . mockUninterestingSeverity . head $ states
-    unexpSev <- mockSetupSTM $ readTVar . mockUnexpectedSeverity . head $ states
+      mockSetupSTM $ readTVar (mockUninterestingSeverity currentState)
+    unexpSev <- mockSetupSTM $ readTVar (mockUnexpectedSeverity currentState)
     case ( full,
            orderedPartial,
            allowedUnexpected unexpected,
diff --git a/src/Test/HMock/MockT.hs b/src/Test/HMock/MockT.hs
--- a/src/Test/HMock/MockT.hs
+++ b/src/Test/HMock/MockT.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE CPP #-}
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE GADTs #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
diff --git a/src/Test/HMock/TH.hs b/src/Test/HMock/TH.hs
--- a/src/Test/HMock/TH.hs
+++ b/src/Test/HMock/TH.hs
@@ -26,7 +26,10 @@
 import Data.Default (Default (..))
 import Data.Either (partitionEithers)
 import qualified Data.Kind
-import Data.List (foldl', (\\))
+import Data.List ((\\))
+#if !MIN_VERSION_base(4, 20, 0)
+import Data.List (foldl')
+#endif
 import Data.Maybe (catMaybes, isNothing)
 import Data.Proxy (Proxy)
 import Data.Typeable (Typeable, typeRep)
diff --git a/test/Classes.hs b/test/Classes.hs
--- a/test/Classes.hs
+++ b/test/Classes.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE CPP #-}
 {-# LANGUAGE DataKinds #-}
 {-# LANGUAGE DefaultSignatures #-}
 {-# LANGUAGE DeriveAnyClass #-}
@@ -33,10 +32,8 @@
 import Test.Predicates (anything, eq, hasSubstr, qMatch, with)
 import Util.DeriveRecursive (deriveRecursive)
 
-#if MIN_VERSION_template_haskell(2, 16, 0)
 -- Pre-define low-level instance to prevent deriveRecursive from trying.
 instance NFData Bytes where rnf = undefined
-#endif
 
 deriveRecursive (Just AnyclassStrategy) ''NFData ''Dec
 
@@ -823,7 +820,7 @@
             expect $
               QReport_
                 anything
-                (hasSubstr "Expected GHC.Types.Int to be a class")
+                (hasSubstr "Int to be a class")
 
             _ <- runQ (makeMockable [t|Int|])
             return ()
diff --git a/test/ExpectSet.hs b/test/ExpectSet.hs
--- a/test/ExpectSet.hs
+++ b/test/ExpectSet.hs
@@ -1,10 +1,13 @@
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 
 module ExpectSet where
 
 import Control.Arrow (second)
+#if !MIN_VERSION_base(4, 20, 0)
 import Data.List (foldl')
+#endif
 import Test.HMock.Internal.ExpectSet
 import Test.HMock.Multiplicity
 import Test.Hspec
diff --git a/test/QuasiMock.hs b/test/QuasiMock.hs
--- a/test/QuasiMock.hs
+++ b/test/QuasiMock.hs
@@ -12,10 +12,6 @@
 import Test.HMock
 import Test.Predicates (anything, elemsAre, eq, is, qMatch)
 
-#if !MIN_VERSION_base(4, 13, 0)
-import Control.Monad.Fail (MonadFail)
-#endif
-
 -- Because not all methods of Quasi are mockable, the instance must be written
 -- by hand.
 instance (Typeable m, MonadFail m, MonadIO m) => Quasi (MockT m) where
@@ -39,9 +35,7 @@
   qAddTempFile s = mockMethod (QAddTempFile s)
   qAddForeignFilePath l s = mockMethod (QAddForeignFilePath l s)
 
-#if MIN_VERSION_template_haskell(2, 16, 0)
   qReifyType n = mockDefaultlessMethod (QReifyType n)
-#endif
 
 #if MIN_VERSION_template_haskell(2, 19, 0)
   qGetPackageRoot = mockDefaultlessMethod QGetPackageRoot
diff --git a/test/QuasiMockBase.hs b/test/QuasiMockBase.hs
--- a/test/QuasiMockBase.hs
+++ b/test/QuasiMockBase.hs
@@ -19,8 +19,9 @@
 import Test.HMock (MakeMockableOptions (..), makeMockableWithOptions)
 import Util.DeriveRecursive (deriveRecursive)
 
-#if MIN_VERSION_template_haskell(2, 16, 0)
+#if !MIN_VERSION_template_haskell(2, 23, 0)
 -- Pre-define low-level instance to prevent deriveRecursive from trying.
+-- template-haskell 2.23 (GHC 9.12) provides this instance itself.
 instance Lift Bytes where lift = undefined; liftTyped = undefined
 #endif
 
diff --git a/test/Util/DeriveRecursive.hs b/test/Util/DeriveRecursive.hs
--- a/test/Util/DeriveRecursive.hs
+++ b/test/Util/DeriveRecursive.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE CPP #-}
 module Util.DeriveRecursive where
 
 import Control.Monad (replicateM)
@@ -5,7 +6,9 @@
 import Control.Monad.State (StateT, evalStateT, gets, modify)
 import Control.Monad.Trans (MonadTrans (lift))
 import Language.Haskell.TH
+#if !MIN_VERSION_base(4, 20, 0)
 import Data.List (foldl')
+#endif
 
 deriveRecursive :: Maybe DerivStrategy -> Name -> Name -> Q [Dec]
 deriveRecursive strat cls ty = evalStateT (concatMapM defineIfNeeded [ty]) []
