diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,15 @@
 # Revision history for replace-attoparsec
 
+## 1.5.0.0 -- 2023-05-30
+
+Upgrade to GHC v9.4.4, text v2.0.1
+
+Text does not work with GHC v9.4.3
+
+Test
+* exitcode-stdio-1.0 instead of detailed-0.9
+* HSpec instead of Cabal Distribution.TestSuite
+
 ## 1.4.5.0 -- 2021-07-29
 
 Add Replace.Attoparsec.Text.Lazy
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -95,6 +95,7 @@
 import Replace.Attoparsec.Text
 import Data.Attoparsec.Text as AT
 import qualified Data.Text as T
+import Control.Applicative
 import Data.Either
 import Data.Char
 ```
@@ -127,7 +128,7 @@
     parens = do
         char '('
         manyTill
-            (void (satisfy $ notInClass "()") <|> void parens)
+            (void parens <|> void anyChar)
             (char ')')
         pure ()
 
@@ -328,30 +329,20 @@
 See [replace-benchmark](https://github.com/jamesdbrock/replace-benchmark)
 for details.
 
-| Program                                           | dense     | sparse   |
-| :---                                              |      ---: |     ---: |
-| [Python 3.7.4 `re.sub`][sub] *repl* function      | 89.23ms   | 23.98ms  |
-| [Perl 5 `s///ge`][s]                              | 180.65ms  | 5.02ms   |
-| [`Replace.Megaparsec.streamEdit`][m] `String`     | 441.94ms  | 375.04ms |
-| [`Replace.Megaparsec.streamEdit`][m] `ByteString` | 529.99ms  | 73.76ms  |
-| [`Replace.Megaparsec.streamEdit`][m] `Text`       | 547.47ms  | 139.21ms |
-| [`Replace.Attoparsec.ByteString.streamEdit`][ab]  | 394.12ms  | 41.13ms  |
-| [`Replace.Attoparsec.Text.streamEdit`][at]        | 515.26ms  | 46.10ms  |
-| [`Text.Regex.Applicative.replace`][ra] `String`   | 1083.98ms | 646.40ms |
-| [`Text.Regex.PCRE.Heavy.gsub`][ph] `Text`         | > 10min   | 14.29ms  |
-| [`Control.Lens.Regex.ByteString.match`][lb]       | > 10min   | 4.27ms   |
-| [`Control.Lens.Regex.Text.match`][lt]             | > 10min   | 14.74ms  |
-
-[sub]: https://docs.python.org/3/library/re.html#re.sub
-[s]: https://perldoc.perl.org/functions/s.html
-[m]: https://hackage.haskell.org/package/replace-megaparsec/docs/Replace-Megaparsec.html#v:streamEdit
-[ab]: https://hackage.haskell.org/package/replace-attoparsec/docs/Replace-Attoparsec-ByteString.html#v:streamEdit
-[at]: https://hackage.haskell.org/package/replace-attoparsec/docs/Replace-Attoparsec-Text.html#v:streamEdit
-[ra]: http://hackage.haskell.org/package/regex-applicative/docs/Text-Regex-Applicative.html#v:replace
-[ss]: http://hackage.haskell.org/package/stringsearch/docs/Data-ByteString-Search.html#v:replace
-[ph]: http://hackage.haskell.org/package/pcre-heavy/docs/Text-Regex-PCRE-Heavy.html#v:gsub
-[lb]: https://hackage.haskell.org/package/lens-regex-pcre/docs/Control-Lens-Regex-ByteString.html#v:match
-[lt]: https://hackage.haskell.org/package/lens-regex-pcre/docs/Control-Lens-Regex-Text.html#v:match
+| Program                                           | dense *ms*  | sparse *ms* |
+| :---                                              |      ---: |     ---:  |
+| Python 3.10.9 [`re.sub`](https://docs.python.org/3/library/re.html#re.sub) *repl* function | 557.22 | 35.47 |
+| Perl  v5.36.0 [`s///ge`](https://perldoc.perl.org/functions/s.html) function | 1208.66 | 12.61 |
+| [`Replace.Megaparsec.streamEdit`](https://hackage.haskell.org/package/replace-megaparsec/docs/Replace-Megaparsec.html#v:streamEdit) `String` | 2921.25 | 2911.81 |
+| [`Replace.Megaparsec.streamEdit`](https://hackage.haskell.org/package/replace-megaparsec/docs/Replace-Megaparsec.html#v:streamEdit) `ByteString` | 3743.25 | 757.21 |
+| [`Replace.Megaparsec.streamEdit`](https://hackage.haskell.org/package/replace-megaparsec/docs/Replace-Megaparsec.html#v:streamEdit) `Text` | 3818.47 | 881.69 |
+| [`Replace.Attoparsec.ByteString.streamEdit`](https://hackage.haskell.org/package/replace-attoparsec/docs/Replace-Attoparsec-ByteString.html#v:streamEdit) | 3006.38 | 179.66 |
+| [`Replace.Attoparsec.Text.streamEdit`](https://hackage.haskell.org/package/replace-attoparsec/docs/Replace-Attoparsec-Text.html#v:streamEdit) | 3062.43 | 300.13 |
+| [`Replace.Attoparsec.Text.Lazy.streamEdit`](https://hackage.haskell.org/package/replace-attoparsec/docs/Replace-Attoparsec-Text-Lazy.html#v:streamEdit) | 3102.15 | 241.58 |
+| [`Text.Regex.Applicative.replace`](http://hackage.haskell.org/package/regex-applicative/docs/Text-Regex-Applicative.html#v:replace) `String` | 13875.25 | 4330.52 |
+| [`Text.Regex.PCRE.Heavy.gsub`](http://hackage.haskell.org/package/pcre-heavy/docs/Text-Regex-PCRE-Heavy.html#v:gsub) `Text` | ∞ | 113.27 |
+| [`Control.Lens.Regex.ByteString.match`](https://hackage.haskell.org/package/lens-regex-pcre/docs/Control-Lens-Regex-ByteString.html#v:match) | ∞ | 117.05 |
+| [`Control.Lens.Regex.Text.match`](https://hackage.haskell.org/package/lens-regex-pcre/docs/Control-Lens-Regex-Text.html#v:match) | ∞ | 35.97 |
 
 
 # Hypothetically Asked Questions
diff --git a/replace-attoparsec.cabal b/replace-attoparsec.cabal
--- a/replace-attoparsec.cabal
+++ b/replace-attoparsec.cabal
@@ -1,7 +1,7 @@
 name:                replace-attoparsec
-version:             1.4.5.0
+version:             1.5.0.0
 cabal-version:       1.18
-synopsis:            Find, replace, and split string patterns with Attoparsec parsers (instead of regex)
+synopsis:            Find, replace, split string patterns with Attoparsec parsers (instead of regex)
 homepage:            https://github.com/jamesdbrock/replace-attoparsec
 bug-reports:         https://github.com/jamesdbrock/replace-attoparsec/issues
 license:             BSD2
@@ -25,39 +25,25 @@
 library
   hs-source-dirs:      src
   build-depends:       base >=4.0 && <5.0
-                     , attoparsec
-                     , bytestring
-                     , text
+                     , attoparsec >=0.4 && <1.0
+                     , bytestring >=0.2 && <1.0
+                     , text >=0.2 && <3.0
   default-language:    Haskell2010
   exposed-modules:     Replace.Attoparsec.Text
                        Replace.Attoparsec.Text.Lazy
                      , Replace.Attoparsec.ByteString
-  ghc-options:         -O2 -Wall
 
-test-suite test-bytestring
-  type:                detailed-0.9
-  test-module:         TestByteString
+test-suite tests
+  type:                exitcode-stdio-1.0
+  main-is:             Tests.hs
+  other-modules:       TestByteString TestText TestTextLazy
   hs-source-dirs:      tests
   default-language:    Haskell2010
   build-depends:       base >= 4.0 && < 5.0
                      , replace-attoparsec
                      , attoparsec
-                     , Cabal
+                     , hspec >=2.0.0 && <3.0.0
+                     , HUnit >=1.1 && <2.0
                      , bytestring
-                     , parsers
-  ghc-options:         -Wall
-
-test-suite test-text
-  type:                detailed-0.9
-  test-module:         TestText
-  other-modules:       TestTextLazy
-  hs-source-dirs:      tests
-  default-language:    Haskell2010
-  build-depends:       base >= 4.0 && < 5.0
-                     , replace-attoparsec
-                     , attoparsec
-                     , Cabal
-                     , text
-                     , parsers
-  ghc-options:         -Wall
-
+                     , text >=0.2 && <3.0
+                     , parsers >=0.1 && < 1.0
diff --git a/tests/TestByteString.hs b/tests/TestByteString.hs
--- a/tests/TestByteString.hs
+++ b/tests/TestByteString.hs
@@ -2,10 +2,10 @@
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE PackageImports #-}
 {-# LANGUAGE CPP #-}
+{-# LANGUAGE BlockArguments #-}
 
 module TestByteString ( tests ) where
 
-import Distribution.TestSuite as TestSuite
 import Data.Attoparsec.ByteString as A
 import Data.Attoparsec.Combinator
 import qualified Data.ByteString as B
@@ -13,118 +13,77 @@
 import "parsers" Text.Parser.Token
 import Replace.Attoparsec.ByteString
 import Control.Applicative
+import Test.Hspec (describe, shouldBe, it, SpecWith)
+import Test.HUnit (assertFailure)
 
 findAllCap' :: Parser a -> Parser [Either B.ByteString (B.ByteString, a)]
 findAllCap' sep = sepCap (match sep)
 
-tests :: IO [Test]
-tests = return
-    [ Test $ runParserTest "findAllCap upperChar"
+tests :: SpecWith ()
+tests = describe "input ByteString" do
+    runParserTest "findAllCap upperChar"
         (findAllCap' upperChar)
         ("aBcD" :: B.ByteString)
         [Left "a", Right ("B", c2w 'B'), Left "c", Right ("D", c2w 'D')]
     -- check that sepCap can progress even when parser consumes nothing
     -- and succeeds.
-    , Test $ runParserTest "zero-consumption parser"
+    runParserTest "zero-consumption parser"
         (sepCap (many upperChar))
         ("aBcD" :: B.ByteString)
         [Left "a", Right [c2w 'B'], Left "c", Right [c2w 'D']]
-    , Test $ runParserTest "scinum"
+    runParserTest "scinum"
         (sepCap scinum)
-        ("1E3")
-        ([Right (1,3)])
-    , Test $ runParserTest "monad fail"
+        "1E3"
+        [Right (1,3)]
+    runParserTest "monad fail"
         (sepCap (fail "" :: Parser ()))
-        ("xxx")
-        ([Left "xxx"])
+        "xxx"
+        [Left "xxx"]
 #if MIN_VERSION_GLASGOW_HASKELL(8,6,0,0)
-    , Test $ runParserTest "read fail"
+    runParserTest "read fail"
         (sepCap (return (read "a" :: Int) :: Parser Int))
         ("a")
         ([Left "a"])
 #endif
-    , Test $ runParserFeed "const string"
+    runParserFeed "const string"
         (sepCap (string "aa"))
-        (" a") ("a ")
-        ([Left " ",Right"aa",Left" "])
-    , Test $ runParserTest "empty input" (sepCap (fail "" :: Parser ())) "" []
-    , Test $ streamEditTest "x to o" (string "x") (const "o") "x x x" "o o o"
-    , Test $ streamEditTest "x to o inner" (string "x") (const "o") " x x x " " o o o "
-    , Test $ streamEditTest "ordering" (string "456") (const "ABC") "123456789" "123ABC789"
-    , Test $ streamEditTest "empty input" (match (fail "")) (fst) "" ""
-    , Test $ breakCapTest "basic" upperChar "aAa" (Just ("a", c2w 'A', "a"))
-    , Test $ breakCapTest "first" upperChar "Aa" (Just ("", c2w 'A', "a"))
-    , Test $ breakCapTest "last" upperChar "aA" (Just ("a", c2w 'A', ""))
-    , Test $ breakCapTest "fail" upperChar "aaa" Nothing
-    , Test $ breakCapTest "match" (match upperChar) "aAa" (Just ("a", ("A",c2w 'A'), "a"))
-    , Test $ breakCapTest "zero-width" (lookAhead upperChar) "aAa" (Just ("a", c2w 'A', "Aa"))
-    , Test $ breakCapTest "empty input" upperChar "" Nothing
-    , Test $ breakCapTest "empty input zero-width" (return () :: Parser ()) "" (Just ("", (), ""))
-    ]
+        " a" "a "
+        [Left " ",Right"aa",Left" "]
+    runParserTest "empty input" (sepCap (fail "" :: Parser ())) "" []
+    streamEditTest "x to o" (string "x") (const "o") "x x x" "o o o"
+    streamEditTest "x to o inner" (string "x") (const "o") " x x x " " o o o "
+    streamEditTest "ordering" (string "456") (const "ABC") "123456789" "123ABC789"
+    streamEditTest "empty input" (match (fail "")) fst "" ""
+    breakCapTest "basic" upperChar "aAa" (Just ("a", c2w 'A', "a"))
+    breakCapTest "first" upperChar "Aa" (Just ("", c2w 'A', "a"))
+    breakCapTest "last" upperChar "aA" (Just ("a", c2w 'A', ""))
+    breakCapTest "fail" upperChar "aaa" Nothing
+    breakCapTest "match" (match upperChar) "aAa" (Just ("a", ("A",c2w 'A'), "a"))
+    breakCapTest "zero-width" (lookAhead upperChar) "aAa" (Just ("a", c2w 'A', "Aa"))
+    breakCapTest "empty input" upperChar "" Nothing
+    breakCapTest "empty input zero-width" (return () :: Parser ()) "" (Just ("", (), ""))
 
   where
-    runParserTest nam p input expected = TestInstance
-            { run = do
-                case parseOnly p input of
-                    Left e -> return (Finished $ TestSuite.Fail $ show e)
-                    Right output ->
-                        if (output == expected)
-                            then return (Finished Pass)
-                            else return (Finished $ TestSuite.Fail
-                                        $ "got " <> show output <> " expected " <> show expected)
-            , name = "parseOnly sepCap " <> nam
-            , tags = []
-            , options = []
-            , setOption = \_ _ -> Left "no options supported"
-            }
+    runParserTest nam p input expected = it nam $ shouldBe
+        (parseOnly p input) (Right expected)
 
-    runParserFeed nam p input1 input2 expected = TestInstance
-            { run = do
-                case parse p input1 of
-                    A.Fail _i _ e -> return (Finished $ TestSuite.Fail $ show e)
-                    A.Partial cont1 -> case cont1 input2 of
-                        A.Fail _i _ e -> return (Finished $ TestSuite.Fail $ show e)
-                        A.Partial cont2 -> case cont2 "" of
-                            A.Fail _i _ e -> return (Finished $ TestSuite.Fail $ show e)
-                            A.Partial _ -> return (Finished $ TestSuite.Fail $ "Should not ask for more input")
-                            A.Done _i output ->
-                                if (output == expected)
-                                    then return (Finished Pass)
-                                    else return (Finished $ TestSuite.Fail
-                                                $ "got " <> show output <> " expected " <> show expected)
-                        A.Done _i _output -> return (Finished $ TestSuite.Fail $ "Should ask for more input")
-                    A.Done _i _output -> return (Finished $ TestSuite.Fail $ "Should ask for more input")
-            , name = "parse Partial sepCap " <> nam
-            , tags = []
-            , options = []
-            , setOption = \_ _ -> Left "no options supported"
-            }
+    runParserFeed nam p input1 input2 expected = it nam do
+        case parse p input1 of
+            A.Fail _i _ e -> assertFailure $ show e
+            A.Partial cont1 -> case cont1 input2 of
+                A.Fail _i _ e -> assertFailure $ show e
+                A.Partial cont2 -> case cont2 "" of
+                    A.Fail _i _ e -> assertFailure $ show e
+                    A.Partial _ -> assertFailure "Should not ask for more input"
+                    A.Done _i output -> output `shouldBe` expected
+                A.Done _i _output -> assertFailure "Should ask for more input"
+            A.Done _i _output -> assertFailure "Should ask for more input"
 
-    streamEditTest nam sep editor input expected = TestInstance
-            { run = do
-                let output = streamEdit sep editor input
-                if (output == expected)
-                    then return (Finished Pass)
-                    else return (Finished $ TestSuite.Fail
-                                $ show output ++ " ≠ " ++ show expected)
-            , name = "streamEdit " ++ nam
-            , tags = []
-            , options = []
-            , setOption = \_ _ -> Left "no options supported"
-            }
+    streamEditTest nam sep editor input expected = it nam $ shouldBe
+        (streamEdit sep editor input) expected
 
-    breakCapTest nam sep input expected = TestInstance
-            { run = do
-                let output = breakCap sep input
-                if (output == expected)
-                    then return (Finished Pass)
-                    else return (Finished $ TestSuite.Fail
-                                $ "got " <> show output <> " expected " <> show expected)
-            , name = "breakCap " ++ nam
-            , tags = []
-            , options = []
-            , setOption = \_ _ -> Left "no options supported"
-            }
+    breakCapTest nam sep input expected = it nam $ shouldBe
+        (breakCap sep input) expected
 
     scinum :: Parser (Double, Integer)
     scinum = do
diff --git a/tests/TestText.hs b/tests/TestText.hs
--- a/tests/TestText.hs
+++ b/tests/TestText.hs
@@ -1,10 +1,10 @@
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE CPP #-}
+{-# LANGUAGE BlockArguments #-}
 
 module TestText ( tests ) where
 
-import Distribution.TestSuite as TestSuite
 import Replace.Attoparsec.Text
 import Data.Attoparsec.Text as A
 import Data.Attoparsec.Combinator
@@ -13,128 +13,86 @@
 import Control.Applicative
 import Data.Bifunctor
 import qualified TestTextLazy
+import Test.Hspec (describe, shouldBe, it, SpecWith)
+import Test.HUnit (assertFailure)
 
 findAllCap' :: Parser a -> Parser [Either T.Text (T.Text, a)]
 findAllCap' sep = sepCap (match sep)
 findAll' :: Parser b -> Parser [Either T.Text T.Text]
-findAll' sep = (fmap.fmap) (second fst) $ sepCap (match sep)
+findAll' sep = fmap (second fst) <$> sepCap (match sep)
 
-tests :: IO [Test]
-tests = liftA2 (<>)
-    (pure
-    [ Test $ runParserTest "findAllCap upperChar"
+tests :: SpecWith ()
+tests = describe "input Text" do
+    runParserTest "findAllCap upperChar"
         (findAllCap' (upper :: Parser Char))
         ("aBcD" :: T.Text)
         [Left "a", Right ("B", 'B'), Left "c", Right ("D", 'D')]
     -- check that sepCap can progress even when parser consumes nothing
     -- and succeeds.
-    , Test $ runParserTest "zero-consumption parser"
+    runParserTest "zero-consumption parser"
         (sepCap (many (upper :: Parser Char)))
         ("aBcD" :: T.Text)
         [Left "a", Right "B", Left "c", Right "D"]
-    , Test $ runParserTest "scinum"
+    runParserTest "scinum"
         (sepCap scinum)
-        ("1E3")
-        ([Right (1,3)])
-    , Test $ runParserTest "monad fail"
+        "1E3"
+        [Right (1,3)]
+    runParserTest "monad fail"
         (sepCap (fail "" :: Parser ()))
-        ("xxx")
-        ([Left "xxx"])
-#if MIN_VERSION_GLASGOW_HASKELL(8,6,0,0)
-    , Test $ runParserTest "read fail"
+        "xxx"
+        [Left "xxx"]
+
+    runParserTest "read fail"
         (sepCap (return (read "a" :: Int) :: Parser Int))
-        ("a")
-        ([Left "a"])
-#endif
-    , Test $ runParserTest "findAll astral"
-        (findAll' ((A.takeWhile (=='𝅘𝅥𝅯') :: Parser T.Text)))
+        "a"
+        [Left "a"]
+
+    runParserTest "findAll astral"
+        (findAll' (A.takeWhile (=='𝅘𝅥𝅯') :: Parser T.Text))
         ("𝄞𝅘𝅥𝅘𝅥𝅘𝅥𝅘𝅥𝅘𝅥𝅯𝅘𝅥𝅯𝅘𝅥𝅯𝅘𝅥𝅯𝅘𝅥𝅘𝅥𝅘𝅥𝅘𝅥" :: T.Text)
         [Left "𝄞𝅘𝅥𝅘𝅥𝅘𝅥𝅘𝅥", Right "𝅘𝅥𝅯𝅘𝅥𝅯𝅘𝅥𝅯𝅘𝅥𝅯", Left "𝅘𝅥𝅘𝅥𝅘𝅥𝅘𝅥"]
-    , Test $ runParserFeed "const string"
+    runParserFeed "const string"
         (sepCap (string "aa"))
-        (" a") ("a ")
-        ([Left " ",Right"aa",Left" "])
-    , Test $ runParserFeed "findAll astral"
-        (findAll' ((A.takeWhile (=='𝅘𝅥𝅯') :: Parser T.Text)))
-        ("𝄞𝅘𝅥𝅘𝅥𝅘𝅥𝅘𝅥𝅘𝅥𝅯𝅘𝅥𝅯") ("𝅘𝅥𝅯𝅘𝅥𝅯𝅘𝅥𝅘𝅥𝅘𝅥𝅘𝅥" :: T.Text)
+        " a" "a "
+        [Left " ",Right "aa",Left " "]
+    runParserFeed "findAll astral"
+        (findAll' (A.takeWhile (=='𝅘𝅥𝅯') :: Parser T.Text))
+        "𝄞𝅘𝅥𝅘𝅥𝅘𝅥𝅘𝅥𝅘𝅥𝅯𝅘𝅥𝅯" ("𝅘𝅥𝅯𝅘𝅥𝅯𝅘𝅥𝅘𝅥𝅘𝅥𝅘𝅥" :: T.Text)
         [Left "𝄞𝅘𝅥𝅘𝅥𝅘𝅥𝅘𝅥", Right "𝅘𝅥𝅯𝅘𝅥𝅯𝅘𝅥𝅯𝅘𝅥𝅯", Left "𝅘𝅥𝅘𝅥𝅘𝅥𝅘𝅥"]
-    , Test $ runParserTest "empty input" (sepCap (fail "" :: Parser ())) "" []
-    , Test $ streamEditTest "x to o" (string "x") (const "o") "x x x" "o o o"
-    , Test $ streamEditTest "x to o inner" (string "x") (const "o") " x x x " " o o o "
-    , Test $ streamEditTest "ordering" (string "456") (const "ABC") "123456789" "123ABC789"
-    , Test $ streamEditTest "empty input" (match (fail "")) (fst) "" ""
-    , Test $ breakCapTest "basic" upper "aAa" (Just ("a", 'A', "a"))
-    , Test $ breakCapTest "first" upper "Aa" (Just ("", 'A', "a"))
-    , Test $ breakCapTest "last" upper "aA" (Just ("a", 'A', ""))
-    , Test $ breakCapTest "fail" upper "aaa" Nothing
-    , Test $ breakCapTest "match" (match upper) "aAa" (Just ("a", ("A",'A'), "a"))
-    , Test $ breakCapTest "zero-width" (lookAhead upper) "aAa" (Just ("a",'A', "Aa"))
-    , Test $ breakCapTest "empty input" upper "" Nothing
-    , Test $ breakCapTest "empty input zero-width" (return () :: Parser ()) "" (Just ("", (), ""))
-    ]) TestTextLazy.tests
+    runParserTest "empty input" (sepCap (fail "" :: Parser ())) "" []
+    streamEditTest "x to o" (string "x") (const "o") "x x x" "o o o"
+    streamEditTest "x to o inner" (string "x") (const "o") " x x x " " o o o "
+    streamEditTest "ordering" (string "456") (const "ABC") "123456789" "123ABC789"
+    streamEditTest "empty input" (match (fail "")) fst "" ""
+    breakCapTest "basic" upper "aAa" (Just ("a", 'A', "a"))
+    breakCapTest "first" upper "Aa" (Just ("", 'A', "a"))
+    breakCapTest "last" upper "aA" (Just ("a", 'A', ""))
+    breakCapTest "fail" upper "aaa" Nothing
+    breakCapTest "match" (match upper) "aAa" (Just ("a", ("A",'A'), "a"))
+    breakCapTest "zero-width" (lookAhead upper) "aAa" (Just ("a",'A', "Aa"))
+    breakCapTest "empty input" upper "" Nothing
+    breakCapTest "empty input zero-width" (return () :: Parser ()) "" (Just ("", (), ""))
   where
-    runParserTest nam p input expected = TestInstance
-            { run = do
-                case parseOnly p input of
-                    Left e -> return (Finished $ TestSuite.Fail $ show e)
-                    Right output ->
-                        if (output == expected)
-                            then return (Finished Pass)
-                            else return (Finished $ TestSuite.Fail
-                                $ "got " <> show output <> " expected " <> show expected)
-            , name = "parseOnly sepCap " <> nam
-            , tags = []
-            , options = []
-            , setOption = \_ _ -> Left "no options supported"
-            }
+    runParserTest nam p input expected = it nam $ shouldBe
+        (parseOnly p input) (Right expected)
 
-    runParserFeed nam p input1 input2 expected = TestInstance
-            { run = do
-                case parse p input1 of
-                    A.Fail _i _ e -> return (Finished $ TestSuite.Fail $ show e)
-                    A.Partial cont1 -> case cont1 input2 of
-                        A.Fail _i _ e -> return (Finished $ TestSuite.Fail $ show e)
-                        A.Partial cont2 -> case cont2 "" of
-                            A.Fail _i _ e -> return (Finished $ TestSuite.Fail $ show e)
-                            A.Partial _ -> return (Finished $ TestSuite.Fail $ "Should not ask for more input")
-                            A.Done _i output ->
-                                if (output == expected)
-                                    then return (Finished Pass)
-                                    else return (Finished $ TestSuite.Fail
-                                        $ "got " <> show output <> " expected " <> show expected)
-                        A.Done _i _output -> return (Finished $ TestSuite.Fail $ "Should ask for more input")
-                    A.Done _i _output -> return (Finished $ TestSuite.Fail $ "Should ask for more input")
-            , name = "parse Partial sepCap " <> nam
-            , tags = []
-            , options = []
-            , setOption = \_ _ -> Left "no options supported"
-            }
+    runParserFeed nam p input1 input2 expected = it nam do
+        case parse p input1 of
+            A.Fail _i _ e -> assertFailure $ show e
+            A.Partial cont1 -> case cont1 input2 of
+                A.Fail _i _ e -> assertFailure $ show e
+                A.Partial cont2 -> case cont2 "" of
+                    A.Fail _i _ e -> assertFailure $ show e
+                    A.Partial _ -> assertFailure "Should not ask for more input"
+                    A.Done _i output -> output `shouldBe` expected
+                A.Done _i _output -> assertFailure "Should ask for more input"
+            A.Done _i _output -> assertFailure "Should ask for more input"
 
-    streamEditTest nam sep editor input expected = TestInstance
-            { run = do
-                let output = streamEdit sep editor input
-                if (output == expected)
-                    then return (Finished Pass)
-                    else return (Finished $ TestSuite.Fail
-                        $ "got " <> show output <> " expected " <> show expected)
-            , name = "streamEdit " ++ nam
-            , tags = []
-            , options = []
-            , setOption = \_ _ -> Left "no options supported"
-            }
+    streamEditTest nam sep editor input expected = it nam $ shouldBe
+                (streamEdit sep editor input) expected
 
-    breakCapTest nam sep input expected = TestInstance
-            { run = do
-                let output = breakCap sep input
-                if (output == expected)
-                    then return (Finished Pass)
-                    else return (Finished $ TestSuite.Fail
-                        $ "got " <> show output <> " expected " <> show expected)
-            , name = "breakCap " ++ nam
-            , tags = []
-            , options = []
-            , setOption = \_ _ -> Left "no options supported"
-            }
+    breakCapTest nam sep input expected = it nam $ shouldBe
+                (breakCap sep input) expected
 
     scinum :: Parser (Double, Integer)
     scinum = do
diff --git a/tests/TestTextLazy.hs b/tests/TestTextLazy.hs
--- a/tests/TestTextLazy.hs
+++ b/tests/TestTextLazy.hs
@@ -1,49 +1,29 @@
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE CPP #-}
+{-# LANGUAGE BlockArguments #-}
 
 -- Basically a copy of TestText with tests for chunks and no tests for partial
 -- input
 module TestTextLazy ( tests ) where
 
-import Distribution.TestSuite as TestSuite
 import Replace.Attoparsec.Text.Lazy
 import Data.Attoparsec.Text.Lazy as A
 import qualified Data.Text.Lazy as T
+import Test.Hspec (describe, shouldBe, it, SpecWith)
 
-tests :: IO [Test]
-tests = return
-    [ Test $ streamEditTest "x to o" (string "x") (const "o") "x x x" "o o o"
-    , Test $ streamEditTest "x to o inner" (string "x") (const "o") " x x x " " o o o "
-    , Test $ streamEditTest "ordering" (string "456") (const "ABC") "123456789" "123ABC789"
-    , Test $ streamEditTest "empty input" (match (fail "")) fst "" ""
-    , Test $ chunkTest "all chunks are processed" (string "x") (const "y") ["x", "x", "x"] id "yyy"
-    , Test $ chunkTest "matches across chunks" (string "abcd") (const "y") ["ab", "cd", "ab", "cd"] id "yy"
-    , Test $ chunkTest "multiple matches in one chunks" (string "x") (const "y") ["xxx", "xx"] id "yyyyy"
-    ]
+tests :: SpecWith ()
+tests = describe "input Text.Lazy" do
+    streamEditTest "x to o" (string "x") (const "o") "x x x" "o o o"
+    streamEditTest "x to o inner" (string "x") (const "o") " x x x " " o o o "
+    streamEditTest "ordering" (string "456") (const "ABC") "123456789" "123ABC789"
+    streamEditTest "empty input" (match (fail "")) fst "" ""
+    chunkTest "all chunks are processed" (string "x") (const "y") ["x", "x", "x"] id "yyy"
+    chunkTest "matches across chunks" (string "abcd") (const "y") ["ab", "cd", "ab", "cd"] id "yy"
+    chunkTest "multiple matches in one chunks" (string "x") (const "y") ["xxx", "xx"] id "yyyyy"
   where
-    chunkTest nam sep editor input post expected = TestInstance
-            { run = do
-                let output = post $ streamEdit sep editor (T.fromChunks input)
-                if (output == expected)
-                    then pure (Finished Pass)
-                    else return (Finished $ TestSuite.Fail
-                        $ "got " <> show (T.toChunks output) <> " expected " <> show (T.toChunks expected))
-            , name = "chunkTest " ++ nam
-            , tags = []
-            , options = []
-            , setOption = \_ _ -> Left "no options supported"
-            }
+    chunkTest nam sep editor input post expected = it nam $ shouldBe
+        (post $ streamEdit sep editor (T.fromChunks input)) expected
 
-    streamEditTest nam sep editor input expected = TestInstance
-            { run = do
-                let output = streamEdit sep editor input
-                if (output == expected)
-                    then return (Finished Pass)
-                    else return (Finished $ TestSuite.Fail
-                        $ "got " <> show output <> " expected " <> show expected)
-            , name = "streamEdit " ++ nam
-            , tags = []
-            , options = []
-            , setOption = \_ _ -> Left "no options supported"
-            }
+    streamEditTest nam sep editor input expected = it nam $ shouldBe
+        (streamEdit sep editor input) expected
diff --git a/tests/Tests.hs b/tests/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Tests.hs
@@ -0,0 +1,14 @@
+{-# LANGUAGE BlockArguments #-}
+
+module Main ( main ) where
+
+import Test.Hspec
+import TestByteString
+import TestText
+import TestTextLazy
+
+main :: IO ()
+main = hspec do
+  TestByteString.tests
+  TestText.tests
+  TestTextLazy.tests
