diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown
--- a/CHANGELOG.markdown
+++ b/CHANGELOG.markdown
@@ -1,3 +1,7 @@
+1.23.2 [2025.06.17]
+-------------------
+* Replace `test-framework` with `tasty` in the test suite.
+
 1.23.1 [2025.03.03]
 -------------------
 * Add `Uniform` and `UniformRange` instances for `Plucker`, `Quaternion`, `V`,
diff --git a/linear.cabal b/linear.cabal
--- a/linear.cabal
+++ b/linear.cabal
@@ -1,6 +1,6 @@
 name:          linear
 category:      Math, Algebra
-version:       1.23.1
+version:       1.23.2
 license:       BSD3
 cabal-version: >= 1.10
 license-file:  LICENSE
@@ -144,10 +144,9 @@
     binary,
     bytestring,
     deepseq,
-    test-framework >= 0.8,
-    test-framework-hunit >= 0.3,
-    test-framework-quickcheck2 >= 0.3,
-    HUnit >= 1.2.5,
+    tasty >= 1.4 && < 1.6,
+    tasty-hunit >= 0.10 && < 0.11,
+    tasty-quickcheck >= 0.10 && < 0.12,
     linear,
     QuickCheck >= 2.5,
     reflection,
diff --git a/tests/Prop/Quaternion.hs b/tests/Prop/Quaternion.hs
--- a/tests/Prop/Quaternion.hs
+++ b/tests/Prop/Quaternion.hs
@@ -4,9 +4,9 @@
 import Linear.Quaternion (Quaternion(..))
 import Linear.Epsilon (nearZero)
 import Linear.Vector (lerp)
-import Test.Framework (Test, testGroup)
-import Test.Framework.Providers.QuickCheck2 (testProperty)
 import Test.QuickCheck (Arbitrary(..))
+import Test.Tasty (TestTree, testGroup)
+import Test.Tasty.QuickCheck (testProperty)
 
 import Prop.V3 ()
 
@@ -19,7 +19,7 @@
 prop_lerp1 :: Quaternion Double -> Quaternion Double -> Bool
 prop_lerp1 a b = nearZero (lerp 1 a b - b)
 
-tests :: [Test]
+tests :: [TestTree]
 tests =
   [ testGroup "lerp"
     [ testProperty "lerp 0 a b == a" prop_lerp0
diff --git a/tests/Test.hs b/tests/Test.hs
--- a/tests/Test.hs
+++ b/tests/Test.hs
@@ -1,25 +1,24 @@
 {-# LANGUAGE CPP #-}
 module Main (main) where
 
-import Test.Framework (defaultMain, testGroup, Test)
-import Test.Framework.Providers.HUnit
+import Test.Tasty (defaultMain, testGroup, TestTree)
 
 import qualified Prop.Quaternion
 import qualified Unit.Binary
 import qualified Unit.Plucker
 import qualified Unit.V
 
-tests :: [Test]
+tests :: [TestTree]
 tests =
   [ testGroup "Property tests"
     [ testGroup "Quaternion" Prop.Quaternion.tests
     ]
   , testGroup "Unit tests"
-    [ testGroup "Binary" $ hUnitTestToTests Unit.Binary.tests
-    , testGroup "Plucker" $ hUnitTestToTests Unit.Plucker.tests
-    , testGroup "V" $ hUnitTestToTests Unit.V.tests
+    [ testGroup "Binary" Unit.Binary.tests
+    , testGroup "Plucker" Unit.Plucker.tests
+    , testGroup "V" Unit.V.tests
     ]
   ]
 
 main :: IO ()
-main = defaultMain tests
+main = defaultMain $ testGroup "linear" tests
diff --git a/tests/Unit/Binary.hs b/tests/Unit/Binary.hs
--- a/tests/Unit/Binary.hs
+++ b/tests/Unit/Binary.hs
@@ -4,7 +4,8 @@
 import Data.Binary.Get
 import Linear
 import qualified Data.ByteString.Lazy as BS
-import Test.HUnit
+import Test.Tasty (TestTree)
+import Test.Tasty.HUnit ((@?=), testCase)
 
 originalVecs :: (V3 Float, V2 Char)
 originalVecs = (V3 1 2 3, V2 'a' 'b')
@@ -13,7 +14,7 @@
 bytes = runPut $ do putLinear $ fst originalVecs
                     putLinear $ snd originalVecs
 
-tests :: Test
-tests = test [ "Serialized length" ~: BS.length bytes ~?= 3*13+2
-             , "Deserialization" ~: deserialized ~?= originalVecs ]
+tests :: [TestTree]
+tests = [ testCase "Serialized length" $ BS.length bytes @?= 3*13+2
+        , testCase "Deserialization" $ deserialized @?= originalVecs ]
   where deserialized = runGet ((,) <$> getLinear <*> getLinear) bytes
diff --git a/tests/Unit/Plucker.hs b/tests/Unit/Plucker.hs
--- a/tests/Unit/Plucker.hs
+++ b/tests/Unit/Plucker.hs
@@ -2,7 +2,8 @@
 import Linear
 import Linear.Plucker
 import Linear.Plucker.Coincides
-import Test.HUnit
+import Test.Tasty (TestTree)
+import Test.Tasty.HUnit ((@?=), testCase)
 
 ln2,ln3,ln4,ln5,ln6,ln7,ln8,ln9 :: Plucker Float
 ln2 = plucker3D (V3 1 3 0) (V3 1 3 (-2))    -- starting line
@@ -14,22 +15,22 @@
 ln8 = plucker3D (V3 0 4 4) (V3 0 (-4) (-4)) -- through origin
 ln9 = Plucker 1 2 3 4 5 6                   -- not a 3D line
 
-tests :: Test
-tests = test [ "parallel" ~: parallel ln2 ln3 ~?= True
-             , "CCW" ~: passes ln2 ln4 ~?= Counterclockwise
-             , "CW" ~: passes ln2 ln5 ~?= Clockwise
-             , "intersect1" ~: intersects ln2 ln6 ~?= True
-             , "intersect2" ~: intersects ln2 ln3 ~?= False
-             , "line equality 1" ~: Line ln2 == Line ln2 ~?= True
-             , "line equality 2" ~: Line ln2 == Line ln7 ~?= True
-             , "line equality 3" ~: Line ln2 == Ray ln7 ~?= True
-             , "line equality 4" ~: Ray ln2 == Line ln7 ~?= True
-             , "ray equality 1" ~: Ray ln2 == Ray ln7 ~?= False
-             , "ray equality 2" ~: Ray ln2 == Ray (3 *^ ln2) ~?= True
-             , "ray equality 3" ~: Ray ln2 == Ray (negate ln7) ~?= True
-             , "quadrance" ~: nearZero (quadranceToOrigin ln2 - 10) ~?= True
-             , "closest 1" ~:
-                 nearZero (qd (V3 1 3 0) $ closestToOrigin ln2) ~?= True
-             , "closest 2" ~: nearZero (qd 0 $ closestToOrigin ln8) ~?= True
-             , "isLine 1" ~: isLine ln2 ~?= True
-             , "isLine 2" ~: isLine ln9 ~?= False ]
+tests :: [TestTree]
+tests = [ testCase "parallel" $ parallel ln2 ln3 @?= True
+        , testCase "CCW" $ passes ln2 ln4 @?= Counterclockwise
+        , testCase "CW" $ passes ln2 ln5 @?= Clockwise
+        , testCase "intersect1" $ intersects ln2 ln6 @?= True
+        , testCase "intersect2" $ intersects ln2 ln3 @?= False
+        , testCase "line equality 1" $ Line ln2 == Line ln2 @?= True
+        , testCase "line equality 2" $ Line ln2 == Line ln7 @?= True
+        , testCase "line equality 3" $ Line ln2 == Ray ln7 @?= True
+        , testCase "line equality 4" $ Ray ln2 == Line ln7 @?= True
+        , testCase "ray equality 1" $ Ray ln2 == Ray ln7 @?= False
+        , testCase "ray equality 2" $ Ray ln2 == Ray (3 *^ ln2) @?= True
+        , testCase "ray equality 3" $ Ray ln2 == Ray (negate ln7) @?= True
+        , testCase "quadrance" $ nearZero (quadranceToOrigin ln2 - 10) @?= True
+        , testCase "closest 1" $
+            nearZero (qd (V3 1 3 0) $ closestToOrigin ln2) @?= True
+        , testCase "closest 2" $ nearZero (qd 0 $ closestToOrigin ln8) @?= True
+        , testCase "isLine 1" $ isLine ln2 @?= True
+        , testCase "isLine 2" $ isLine ln9 @?= False ]
diff --git a/tests/Unit/V.hs b/tests/Unit/V.hs
--- a/tests/Unit/V.hs
+++ b/tests/Unit/V.hs
@@ -4,10 +4,11 @@
 import Control.DeepSeq (rnf)
 import qualified Data.Vector.Unboxed as U (fromList)
 import Linear.V (V)
-import Test.HUnit
+import Test.Tasty (TestTree)
+import Test.Tasty.HUnit ((@?=), testCase)
 
 v10 :: V 10 Int
 v10 = return 5
 
-tests :: Test
-tests = test [ "GH124" ~: rnf (U.fromList [v10]) ~?= () ]
+tests :: [TestTree]
+tests = [ testCase "GH124" $ rnf (U.fromList [v10]) @?= () ]
