diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,6 +1,25 @@
 Changelog for script-monad
 ==========================
 
+Unreleased Changes
+------------------
+
+
+
+0.0.4
+-----
+
+* Added
+    * `MonadFail` instances for `ScriptTT` and `HttpTT`
+* Changed
+    * `Eq` instance for `HttpResponse`; see https://github.com/snoyberg/http-client/issues/433
+    * Relax types of `checkScriptTT` and `checkHttpTT`
+    * Relax lower bounds of dependencies to support stack resolvers as old as lts-13.0. (This is a hard lower bound because we require QuantifiedConstraints.)
+    * Support aeson-2.0.0.0 and http-client-0.7.0
+    * Replace String with Text
+
+
+
 0.0.3
 -----
 
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,8 +1,6 @@
 script-monad
 ============
 
-[![Build Status](https://travis-ci.org/nbloomf/script-monad.svg?branch=master)](https://travis-ci.org/nbloomf/script-monad)
-
 Haskell library providing an unrolled stack of reader, writer, state, error, and prompt monads. Handy for designing extensible and testable DSLs.
 
 As an example, also provides a basic monad transformer for HTTP interactions.
diff --git a/script-monad.cabal b/script-monad.cabal
--- a/script-monad.cabal
+++ b/script-monad.cabal
@@ -1,5 +1,5 @@
 name:           script-monad
-version:        0.0.3
+version:        0.0.4
 description:    Please see the README on GitHub at <https://github.com/nbloomf/script-monad#readme>
 homepage:       https://github.com/nbloomf/script-monad#readme
 bug-reports:    https://github.com/nbloomf/script-monad/issues
@@ -26,21 +26,21 @@
   hs-source-dirs: src
 
   build-depends:
-      base >=4.7 && <5
+      base >=4.11.1.0 && <5
 
-    , aeson >=1.2.4.0
-    , aeson-pretty >=0.8.5
+    , aeson >=1.3.1.1
+    , aeson-pretty >=0.8.7
     , bytestring >=0.10.8.2
-    , http-client >=0.5.10
-    , http-types >=0.12.1
+    , http-client >=0.5.14
+    , http-types >=0.12.2
     , lens >=4.16
     , lens-aeson >=1.0.2
-    , QuickCheck >=2.10.1
-    , text >=1.2.3.0
+    , QuickCheck >=2.11.3
+    , text >=1.2.3.1
     , time >=1.8.0.2
-    , transformers >=0.5.2.0
+    , transformers >=0.5.5.0
     , unordered-containers >=0.2.9.0
-    , vector >=0.12.0.1
+    , vector >=0.12.0.2
     , wreq >=0.5.2
 
   exposed-modules:
@@ -62,7 +62,7 @@
   hs-source-dirs: app
   ghc-options: -threaded -rtsopts -with-rtsopts=-N
   build-depends:
-      base >=4.7 && <5
+      base >=4.11.1.0 && <5
     , script-monad
 
 
@@ -75,19 +75,24 @@
   ghc-options: -threaded -rtsopts -with-rtsopts=-N
 
   build-depends:
-      base >=4.7 && <5
+      base >=4.11.1.0 && <5
     , script-monad
 
     , bytestring >=0.10.8.2
+    , http-client >=0.5.14
+    , http-types >=0.12.2
+    , QuickCheck >=2.12.6.1
     , tasty >=1.0.1.1
     , tasty-hunit >=0.10.0.1
     , tasty-quickcheck >=0.9.2
-    , tasty-quickcheck-laws >= 0.0.1
+    , tasty-quickcheck-laws >=0.0.1
+    , text >=1.2.3.1
     , transformers >=0.5.2.0
 
   other-modules:
       Control.Monad.Script.Test
     , Control.Monad.Script.Http.Test
+    , Data.Text.Extras
     , Data.MockIO.Test
     , Data.MockIO.Test.Server
     , Data.MockIO.FileSystem.Test
diff --git a/src/Control/Monad/Script.hs b/src/Control/Monad/Script.hs
--- a/src/Control/Monad/Script.hs
+++ b/src/Control/Monad/Script.hs
@@ -16,6 +16,7 @@
 
 {-#
   LANGUAGE
+    CPP,
     GADTs,
     Rank2Types,
     TupleSections, 
@@ -68,6 +69,10 @@
 
 
 
+#if MIN_VERSION_base(4,9,0)
+import Prelude hiding (fail)
+#endif
+
 import Control.Monad
   ( ap, join )
 import Control.Monad.Trans.Class
@@ -83,14 +88,19 @@
 import Data.Typeable
   ( Typeable )
 import Test.QuickCheck
-  ( Property, Gen, Arbitrary(..), CoArbitrary(..) )
+  ( Property, Gen, Arbitrary(..), CoArbitrary(..), Testable )
 import Test.QuickCheck.Monadic
-  ( monadicIO, run, assert )
+  ( PropertyM, monadicIO, run, stop, assert )
 
+-- Transitional MonadFail implementation
+#if MIN_VERSION_base(4,9,0)
+import Control.Monad.Fail
+#endif
 
 
 
 
+
 -- | Opaque stack of error (@e@), reader (@r@), writer (@w@), state (@s@), and prompt (@p@) monad transformers, accepting a monad transformer parameter (@t@). Behaves something like a monad transformer transformer.
 data
   ScriptTT
@@ -142,6 +152,11 @@
     runScriptTT x (s0,r) g cont
 
 instance
+  (Monoid w, Monad eff, Monad (t eff), MonadTrans t, MonadFail (t eff))
+    => MonadFail (ScriptTT e r w s p t eff) where
+  fail msg = ScriptTT $ \_ -> \_ _ -> fail msg
+
+instance
   (Monoid w, Monad eff, Monad (t eff), MonadTrans t)
     => Applicative (ScriptTT e r w s p t eff) where
   pure = return
@@ -201,18 +216,23 @@
 
 -- | Turn a `ScriptTT` with a monadic evaluator into a `Property`; for testing with QuickCheck. Wraps `execScriptTT`.
 checkScriptTT
-  :: (Monad eff, Monad (t eff), MonadTrans t, Show q)
+  :: forall eff t q prop e r w s p a
+   . (Monad eff, Monad (t eff), MonadTrans t, Show q, Testable prop)
   => s -- ^ Initial state
   -> r -- ^ Environment
   -> (forall u. p u -> eff u) -- ^ Moandic effect evaluator
   -> (t eff (Either e a, s, w) -> IO q) -- ^ Condense to `IO`
-  -> (q -> Bool) -- ^ Result check
+  -> (q -> prop) -- ^ Result check
   -> ScriptTT e r w s p t eff a
   -> Property
-checkScriptTT s r eval cond check script = monadicIO $ do
-  let result = execScriptTT s r eval script
-  q <- run $ cond result
-  assert $ check q
+checkScriptTT s r eval cond check script =
+  let
+    action :: PropertyM IO prop
+    action = do
+      let result = execScriptTT s r eval script
+      q <- run $ cond result
+      stop $ check q
+  in monadicIO action
 
 
 
diff --git a/src/Control/Monad/Script/Http.hs b/src/Control/Monad/Script/Http.hs
--- a/src/Control/Monad/Script/Http.hs
+++ b/src/Control/Monad/Script/Http.hs
@@ -12,9 +12,11 @@
 
 {-#
   LANGUAGE
+    CPP,
     GADTs,
     Rank2Types,
     RecordWildCards,
+    OverloadedStrings,
     QuantifiedConstraints
 #-}
 
@@ -38,7 +40,7 @@
   , catchIOException
   , catchAnyError
   , printError
-  , E()
+  , E(..)
 
   -- * Reader
   , ask
@@ -109,7 +111,13 @@
   , checkHttpTT
 ) where
 
+
+
+#if MIN_VERSION_base(4,9,0)
+import Prelude hiding (fail, lookup)
+#else
 import Prelude hiding (lookup)
+#endif
 
 import Control.Applicative
   ( Applicative(..), (<$>) )
@@ -120,7 +128,7 @@
 import Control.Exception
   ( IOException, Exception, try )
 import Control.Monad
-  ( Functor(..), Monad(..), ap )
+  ( Functor(..), Monad((>>=),return), ap )
 import Control.Monad.Trans.Class
   ( MonadTrans(..) )
 import Control.Monad.Trans.Identity
@@ -134,13 +142,11 @@
 import Data.Aeson.Lens
   ( _Value )
 import Data.ByteString.Lazy
-  ( ByteString, fromStrict, readFile, writeFile )
+  ( ByteString, fromStrict, readFile, writeFile, toStrict )
 import Data.ByteString.Lazy.Char8
   ( unpack, pack )
 import Data.Functor.Identity
   ( Identity() )
-import Data.HashMap.Strict
-  ( lookup )
 import Data.IORef
   ( IORef, newIORef, readIORef, writeIORef )
 import Data.List
@@ -149,6 +155,9 @@
   ( fromString )
 import Data.Text
   ( Text )
+import qualified Data.Text as T
+import qualified Data.Text.IO as T
+import qualified Data.Text.Encoding as T
 import Data.Time
   ( UTCTime )
 import Data.Time.Clock.System
@@ -175,11 +184,25 @@
 import System.IO.Error
   ( ioeGetFileName, ioeGetLocation, ioeGetErrorString )
 import Test.QuickCheck
-  ( Property, Arbitrary(..), Gen )
+  ( Property, Arbitrary(..), Gen, Testable )
 
 import qualified Control.Monad.Script as S
 import Network.HTTP.Client.Extras
 import Data.Aeson.Extras
+
+-- aeson 2.0.0.0 introduced KeyMap over HashMap
+#if MIN_VERSION_aeson(2,0,0)
+import Data.Aeson.Key (fromText)
+import Data.Aeson.KeyMap (lookup)
+#else
+import Data.HashMap.Strict (lookup)
+#endif
+
+-- Transitional MonadFail implementation
+#if MIN_VERSION_base(4,9,0)
+import Control.Monad.Fail
+#endif
+
 import Data.LogSeverity
 import Data.MockIO
 import Data.MockIO.FileSystem
@@ -212,6 +235,11 @@
   (HttpTT x) >>= f = HttpTT (x >>= (httpTT . f))
 
 instance
+  (Monad eff, Monad (t eff), MonadTrans t, MonadFail (t eff))
+    => MonadFail (HttpTT e r w s p t eff) where
+  fail = HttpTT . fail
+
+instance
   (MonadTrans t, forall m. (Monad m) => Monad (t m))
     => MonadTrans (HttpTT e r w s p t) where
   lift = HttpTT . lift
@@ -238,12 +266,13 @@
 
 -- | Turn an `HttpTT` into a property; for testing with QuickCheck.
 checkHttpTT
-  :: (Monad eff, Monad (t eff), MonadTrans t, Show q)
+  :: forall eff t q e r w s p a prop
+   . (Monad eff, Monad (t eff), MonadTrans t, Show q, Testable prop)
   => S s -- ^ Initial state
   -> R e w r -- ^ Environment
   -> (forall u. P p u -> eff u) -- ^ Effect evaluator
   -> (t eff (Either (E e) a, S s, W e w) -> IO q) -- ^ Condense to `IO`
-  -> (q -> Bool) -- ^ Result check
+  -> (q -> prop) -- ^ Result check
   -> HttpTT e r w s p t eff a
   -> Property
 checkHttpTT s r eval cond check =
@@ -369,12 +398,12 @@
   deriving Show
 
 -- | Pretty printer for errors
-printError :: (e -> String) -> E e -> String
+printError :: (e -> Text) -> E e -> Text
 printError p err = case err of
-  E_Http e -> unlines [ "HTTP Exception:", show e ]
-  E_IO e -> unlines [ "IO Exception:", show e ]
-  E_Json e -> unlines [ "JSON Error:", show e ]
-  E e -> unlines [ "Error:", p e ]
+  E_Http e -> T.unlines [ "HTTP Exception:", T.pack $ show e ]
+  E_IO e -> T.unlines [ "IO Exception:", T.pack $ show e ]
+  E_Json e -> T.unlines [ "JSON Error:", T.pack $ show e ]
+  E e -> T.unlines [ "Error:", p e ]
 
 -- | Also logs the exception.
 throwHttpException
@@ -479,7 +508,7 @@
   { _logOptions :: LogOptions e w
 
   -- | Printer for log entries.
-  , _logEntryPrinter :: LogOptions e w -> LogEntry e w -> Maybe String
+  , _logEntryPrinter :: LogOptions e w -> LogEntry e w -> Maybe Text
 
   -- | Handle for printing logs
   , _logHandle :: Handle
@@ -488,7 +517,7 @@
   , _logLock :: Maybe (MVar ())
 
   -- | Identifier string for the session; used to help match log entries emitted by the same session.
-  , _uid :: String
+  , _uid :: Text
 
   -- | Function for elevating 'HttpException's to a client-supplied error type.
   , _httpErrorInject :: HttpException -> Maybe e 
@@ -544,10 +573,10 @@
   , _logHeaders :: Bool
 
     -- | Printer for client-supplied error type. The boolean toggles JSON pretty printing.
-  , _printUserError :: Bool -> e -> String
+  , _printUserError :: Bool -> e -> Text
 
     -- | Printer for client-supplied log type. the boolean toggles JSON pretty printing.
-  , _printUserLog :: Bool -> w -> String
+  , _printUserLog :: Bool -> w -> Text
   }
 
 -- | Noisy, in color, without parsing JSON responses, and using `Show` instances for user-supplied error and log types.
@@ -558,8 +587,8 @@
   , _logSilent = False
   , _logMinSeverity = LogDebug
   , _logHeaders = True
-  , _printUserError = \_ e -> show e
-  , _printUserLog = \_ w -> show w
+  , _printUserError = \_ e -> T.pack $ show e
+  , _printUserLog = \_ w -> T.pack $ show w
   }
 
 -- | Noisy, in color, without parsing JSON responses, and using trivial printers for user-supplied error and log types. For testing.
@@ -578,20 +607,21 @@
 basicLogEntryPrinter
   :: LogOptions e w
   -> LogEntry e w
-  -> Maybe String
+  -> Maybe Text
 basicLogEntryPrinter opt@LogOptions{..} LogEntry{..} =
   if _logSilent || (_logEntrySeverity < _logMinSeverity)
     then Nothing
     else
       let
+        colorize :: Text -> Text
         colorize msg = if _logColor
           then colorBySeverity _logEntrySeverity msg
           else msg
 
-        timestamp :: String
-        timestamp = take 19 $ show _logEntryTimestamp
+        timestamp :: Text
+        timestamp = T.pack $ take 19 $ show _logEntryTimestamp
       in
-        Just $ unwords $ filter (/= "")
+        Just $ T.unwords $ filter (/= "")
           [ colorize timestamp
           , _logEntryUID
           , logEntryTitle _logEntry
@@ -614,14 +644,14 @@
 
 data LogEntry e w = LogEntry
   { _logEntryTimestamp :: UTCTime
-  , _logEntryUID :: String
+  , _logEntryUID :: Text
   , _logEntrySeverity :: LogSeverity
   , _logEntry :: Log e w
   } deriving Show
 
 -- | Log entry type
 data Log e w
-  = L_Comment String
+  = L_Comment Text
   | L_Request HttpVerb Url Wreq.Options (Maybe ByteString)
   | L_SilentRequest
   | L_Response HttpResponse
@@ -662,7 +692,7 @@
   :: Handle
   -> Maybe (MVar ())
   -> LogOptions e w
-  -> (LogOptions e w -> LogEntry e w -> Maybe String)
+  -> (LogOptions e w -> LogEntry e w -> Maybe Text)
   -> W e w
   -> IO ()
 printHttpLogs handle lock opts printer (W ws) = do
@@ -672,8 +702,8 @@
         Nothing -> return ()
         Just str -> do
           case lock of
-            Just lock -> withMVar lock (\() -> System.IO.hPutStrLn handle str)
-            Nothing -> System.IO.hPutStrLn handle str
+            Just lock -> withMVar lock (\() -> T.hPutStrLn handle str)
+            Nothing -> T.hPutStrLn handle str
           hFlush handle
 
   if _logSilent opts
@@ -690,8 +720,8 @@
   E_Json err -> L_JsonError err
   E e -> L_Error e
 
-type LogEntryTitle = String
-type LogEntryBody = String
+type LogEntryTitle = Text
+type LogEntryBody = Text
 
 logEntryBody
   :: LogOptions e w
@@ -702,65 +732,80 @@
 
   L_Request verb url opt payload ->
     let
+      head :: Text
       head = case (_logJson, _logHeaders) of
-        (True,  True)  -> unpack $ encodePretty $ jsonResponseHeaders $ opt ^. Wreq.headers
-        (False, True)  -> show $ opt ^. Wreq.headers
+        (True,  True)  -> T.decodeUtf8 $ toStrict $
+          encodePretty $ jsonResponseHeaders $ opt ^. Wreq.headers
+        (False, True)  -> T.pack $ show $ opt ^. Wreq.headers
         (_,     False) -> ""
 
+      body :: Text
       body = case (_logJson, payload) of
         (True,  Just p)  -> case decode p of
-          Nothing -> "JSON parse error:\n" ++ unpack p
-          Just v -> unpack $ encodePretty (v :: Value)
-        (False, Just p)  -> unpack p
+          Nothing -> "JSON parse error:\n" <> T.decodeUtf8 (toStrict p)
+          Just v -> T.decodeUtf8 $ toStrict $ encodePretty (v :: Value)
+        (False, Just p)  -> T.decodeUtf8 $ toStrict p
         (_,     Nothing) -> ""
 
     in
-      intercalate "\n" $ filter (/= "") [unwords ["Request", show verb, url], head, body]
+      T.intercalate "\n" $ filter (/= "")
+        [ T.unwords ["Request", T.pack $ show verb, url]
+        , head
+        , body
+        ]
 
   L_SilentRequest -> ""
 
   L_Response response ->
     let
+      head :: Text
       head = case (_logJson, _logHeaders) of
-        (True,  True)  -> unpack $ encodePretty $ jsonResponseHeaders $ _responseHeaders response
-        (False, True)  -> show $ _responseHeaders response
+        (True,  True)  -> T.decodeUtf8 $ toStrict $
+          encodePretty $ jsonResponseHeaders $ _responseHeaders response
+        (False, True)  -> T.pack $ show $ _responseHeaders response
         (_,     False) -> ""
 
+      body :: Text
       body = case _logJson of
-        True  -> unpack $ encodePretty $ preview _Value $ _responseBody response
-        False -> show response
+        True  -> T.decodeUtf8 $ toStrict $
+          encodePretty $ preview _Value $ _responseBody response
+        False -> T.pack $ show response
 
     in
-      intercalate "\n" $ filter (/= "") ["Response", head, body]
+      T.intercalate "\n" $ filter (/= "") ["Response", head, body]
 
   L_SilentResponse -> ""
 
-  L_Pause k -> "Wait for " ++ show k ++ "μs"
+  L_Pause k -> "Wait for " <> T.pack (show k) <> "μs"
 
   L_HttpError e -> if _logJson
     then
       let
-        unpackHttpError :: HttpException -> Maybe (String, String)
+        unpackHttpError :: HttpException -> Maybe (Text, Text)
         unpackHttpError err = case err of
           HttpExceptionRequest _ (StatusCodeException s r) -> do
             json <- decode $ fromStrict r
             let status = s ^. Wreq.responseStatus 
-            return (show status, unpack $ encodePretty (json :: Value))
+            return (T.pack $ show status, T.decodeUtf8 $ toStrict $ encodePretty (json :: Value))
           _ -> Nothing
       in
         case unpackHttpError e of
-          Nothing -> show e
-          Just (code, json) -> intercalate "\n" [ unwords [ "HTTP Error Response", code], json ]
+          Nothing -> T.pack $ show e
+          Just (code, json) -> T.intercalate "\n" [ T.unwords [ "HTTP Error Response", code], json ]
 
-    else show e
+    else T.pack $ show e
 
-  L_IOError e -> unwords [ show $ ioeGetFileName e, ioeGetLocation e, ioeGetErrorString e ]
+  L_IOError e -> T.unwords
+    [ T.pack $ show $ ioeGetFileName e
+    , T.pack $ ioeGetLocation e
+    , T.pack $ ioeGetErrorString e
+    ]
 
-  L_JsonError e -> show e
+  L_JsonError e -> T.pack $ show e
 
-  L_Error e -> unwords [ _printUserError _logJson e ]
+  L_Error e -> T.unwords [ _printUserError _logJson e ]
 
-  L_Log w -> unwords [ _printUserLog _logJson w ]
+  L_Log w -> T.unwords [ _printUserLog _logJson w ]
 
 
 
@@ -795,10 +840,10 @@
 -- | Atomic effects
 data P p a where
   HPutStrLn
-    :: Handle -> String
+    :: Handle -> Text
     -> P p (Either IOException ())
   HPutStrLnBlocking
-    :: MVar () -> Handle -> String
+    :: MVar () -> Handle -> Text
     -> P p (Either IOException ())
 
   GetSystemTime :: P p UTCTime
@@ -823,28 +868,34 @@
   -> IO a
 evalIO eval x = case x of
   HPutStrLn handle string -> try $ do
-    System.IO.hPutStrLn handle string
+    T.hPutStrLn handle string
     hFlush handle
 
   HPutStrLnBlocking lock handle str -> try $ do
-    withMVar lock (\() -> System.IO.hPutStrLn handle str)
+    withMVar lock (\() -> T.hPutStrLn handle str)
     hFlush handle
 
   GetSystemTime -> fmap systemToUTCTime getSystemTime
 
   ThreadDelay k -> threadDelay k
 
-  HttpGet opts s url -> case s of
-    Nothing -> try $ readHttpResponse <$> Wreq.getWith opts url
-    Just sn -> try $ readHttpResponse <$> S.getWith opts sn url
+  HttpGet opts s url ->
+    let url' = T.unpack url
+    in case s of
+      Nothing -> try $ readHttpResponse <$> Wreq.getWith opts url'
+      Just sn -> try $ readHttpResponse <$> S.getWith opts sn url'
 
-  HttpPost opts s url msg -> case s of
-    Nothing -> try $ readHttpResponse <$> Wreq.postWith opts url msg
-    Just sn -> try $ readHttpResponse <$> S.postWith opts sn url msg
+  HttpPost opts s url msg ->
+    let url' = T.unpack url
+    in case s of
+      Nothing -> try $ readHttpResponse <$> Wreq.postWith opts url' msg
+      Just sn -> try $ readHttpResponse <$> S.postWith opts sn url' msg
 
-  HttpDelete opts s url -> case s of
-    Nothing -> try $ readHttpResponse <$> Wreq.deleteWith opts url
-    Just sn -> try $ readHttpResponse <$> S.deleteWith opts sn url
+  HttpDelete opts s url ->
+    let url' = T.unpack url
+    in case s of
+      Nothing -> try $ readHttpResponse <$> Wreq.deleteWith opts url'
+      Just sn -> try $ readHttpResponse <$> S.deleteWith opts sn url'
 
   P act -> eval act
 
@@ -857,12 +908,12 @@
   HPutStrLn handle str -> do
     incrementTimer 1
     fmap Right $ modifyMockWorld $ \w -> w
-      { _files = appendLines (Right handle) (lines str) $ _files w }
+      { _files = appendLines (Right handle) (T.lines str) $ _files w }
 
   HPutStrLnBlocking _ handle str -> do
     incrementTimer 1
     fmap Right $ modifyMockWorld $ \w -> w
-      { _files = appendLines (Right handle) (lines str) $ _files w }
+      { _files = appendLines (Right handle) (T.lines str) $ _files w }
 
   GetSystemTime -> do
     incrementTimer 1
@@ -918,7 +969,7 @@
 -- | Write a comment to the log
 comment
   :: (Monad eff, Monad (t eff), MonadTrans t)
-  => String
+  => Text
   -> HttpTT e r w s p t eff ()
 comment msg = logNow LogInfo $ L_Comment msg
 
@@ -1002,7 +1053,7 @@
 hPutStrLn
   :: (Monad eff, Monad (t eff), MonadTrans t)
   => Handle
-  -> String
+  -> Text
   -> HttpTT e r w s p t eff ()
 hPutStrLn h str = do
   result <- prompt $ HPutStrLn h str
@@ -1015,7 +1066,7 @@
   :: (Monad eff, Monad (t eff), MonadTrans t)
   => MVar ()
   -> Handle
-  -> String
+  -> Text
   -> HttpTT e r w s p t eff ()
 hPutStrLnBlocking lock h str = do
   result <- prompt $ HPutStrLnBlocking lock h str
@@ -1153,9 +1204,16 @@
   -> Value -- ^ JSON object
   -> HttpTT e r w s p t eff Value
 lookupKeyJson key v = case v of
-  Object obj -> case lookup key obj of
-    Nothing -> throwJsonError $ JsonKeyDoesNotExist key (Object obj)
-    Just value -> return value
+  Object obj ->
+    let
+#if MIN_VERSION_aeson(2,0,0)
+      val = lookup (fromText key) obj
+#else
+      val = lookup key obj
+#endif
+    in case val of
+      Nothing -> throwJsonError $ JsonKeyDoesNotExist key (Object obj)
+      Just value -> return value
   _ -> throwJsonError $ JsonKeyLookupOffObject key v
 
 -- | Decode a `A.Value` to some other type.
diff --git a/src/Data/Aeson/Extras.hs b/src/Data/Aeson/Extras.hs
--- a/src/Data/Aeson/Extras.hs
+++ b/src/Data/Aeson/Extras.hs
@@ -21,7 +21,7 @@
 import Data.String
   ( fromString )
 import Data.Text
-  ( Text )
+  ( Text, pack )
 import Test.QuickCheck
   ( Arbitrary(..), Gen )
 
@@ -30,7 +30,7 @@
 -- | Represents the kinds of errors that can occur when parsing and decoding JSON.
 data JsonError
   -- | A generic JSON error; try not to use this.
-  = JsonError String
+  = JsonError Text
   -- | A failed parse.
   | JsonParseError ByteString
   -- | An attempt to look up the value of a key that does not exist on an object.
@@ -43,9 +43,10 @@
 
 instance Arbitrary JsonError where
   arbitrary = do
+    let arbText = pack <$> arbitrary
     k <- arbitrary :: Gen Int
     case k `mod` 5 of
-      0 -> JsonError <$> arbitrary
+      0 -> JsonError <$> arbText
       1 -> JsonParseError <$> (fromString <$> arbitrary)
       2 -> JsonKeyDoesNotExist <$>
              (fromString <$> arbitrary) <*>
diff --git a/src/Data/LogSeverity.hs b/src/Data/LogSeverity.hs
--- a/src/Data/LogSeverity.hs
+++ b/src/Data/LogSeverity.hs
@@ -11,11 +11,14 @@
 -}
 
 
+{-# LANGUAGE OverloadedStrings #-}
 module Data.LogSeverity (
     LogSeverity(..)
   , colorBySeverity
 ) where
 
+import Data.Text
+
 -- | [Syslog](https://en.wikipedia.org/wiki/Syslog) style log severities.
 data LogSeverity
   = LogDebug -- ^ Debug-level messages
@@ -31,14 +34,14 @@
 -- | Pretty prints a simple log header.
 colorBySeverity
   :: LogSeverity
-  -> String -- ^ Printed before the severity label; i.e. a timestamp
-  -> String
+  -> Text -- ^ Printed before the severity label; i.e. a timestamp
+  -> Text
 colorBySeverity severity msg = case severity of
-  LogDebug -> "\x1b[1;32m" ++ msg ++ " DEBUG \x1b[0;39;49m"
-  LogInfo -> "\x1b[1;32m" ++ msg ++ " INFO \x1b[0;39;49m"
-  LogNotice -> "\x1b[1;34m" ++ msg ++ " NOTICE \x1b[0;39;49m"
-  LogWarning -> "\x1b[1;33m" ++ msg ++ " WARNING \x1b[0;39;49m"
-  LogError -> "\x1b[1;31m" ++ msg ++ " ERROR \x1b[0;39;49m"
-  LogCritical -> "\x1b[1;31m" ++ msg ++ " CRITICAL \x1b[0;39;49m"
-  LogAlert -> "\x1b[1;35m" ++ msg ++ " ALERT \x1b[0;39;49m"
-  LogEmergency -> "\x1b[1;35m" ++ msg ++ " EMERGENCY \x1b[0;39;49m"
+  LogDebug     -> "\x1b[1;32m" <> msg <> " DEBUG \x1b[0;39;49m"
+  LogInfo      -> "\x1b[1;32m" <> msg <> " INFO \x1b[0;39;49m"
+  LogNotice    -> "\x1b[1;34m" <> msg <> " NOTICE \x1b[0;39;49m"
+  LogWarning   -> "\x1b[1;33m" <> msg <> " WARNING \x1b[0;39;49m"
+  LogError     -> "\x1b[1;31m" <> msg <> " ERROR \x1b[0;39;49m"
+  LogCritical  -> "\x1b[1;31m" <> msg <> " CRITICAL \x1b[0;39;49m"
+  LogAlert     -> "\x1b[1;35m" <> msg <> " ALERT \x1b[0;39;49m"
+  LogEmergency -> "\x1b[1;35m" <> msg <> " EMERGENCY \x1b[0;39;49m"
diff --git a/src/Data/MockIO.hs b/src/Data/MockIO.hs
--- a/src/Data/MockIO.hs
+++ b/src/Data/MockIO.hs
@@ -49,6 +49,8 @@
   ( ap )
 import Data.ByteString.Lazy
   ( ByteString, pack )
+import Data.Text
+  ( Text )
 import Data.Time
   ( UTCTime(..), Day(..), diffTimeToPicoseconds )
 import Data.Time.Clock
@@ -115,9 +117,9 @@
   { _files :: FileSystem (Either FilePath Handle)
   , _time :: UTCTime
 
-  , _httpGet :: String -> MockNetwork s HttpResponse
-  , _httpPost :: String -> ByteString -> MockNetwork s HttpResponse
-  , _httpDelete :: String -> MockNetwork s HttpResponse
+  , _httpGet :: Text -> MockNetwork s HttpResponse
+  , _httpPost :: Text -> ByteString -> MockNetwork s HttpResponse
+  , _httpDelete :: Text -> MockNetwork s HttpResponse
 
   , _serverState :: MockServer s
   }
diff --git a/src/Data/MockIO/FileSystem.hs b/src/Data/MockIO/FileSystem.hs
--- a/src/Data/MockIO/FileSystem.hs
+++ b/src/Data/MockIO/FileSystem.hs
@@ -10,7 +10,7 @@
 A fake filesystem for testing.
 -}
 
-{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE ScopedTypeVariables, OverloadedStrings #-}
 module Data.MockIO.FileSystem (
     FileSystem(..)
   , File(..)
@@ -26,21 +26,24 @@
 
 import Data.Maybe
 import Data.List
+import Data.Text (Text)
+import qualified Data.Text as T
+import qualified Data.Text.IO as T
 
 import Test.QuickCheck
-  ( Arbitrary(..), Positive(..), Gen, vectorOf )
+  ( Arbitrary(..), Positive(..), Gen, vectorOf, listOf )
 
 
 
 -- | Abstraction of a text file consisting of a "handle" and a list of lines.
 data File a = File
   { _fileHandle :: a -- ^ File "handle"
-  , _fileContents :: [String] -- ^ List of lines
+  , _fileContents :: [Text] -- ^ List of lines
   } deriving Eq
 
 instance (Show a) => Show (File a) where
-  show (File h lns) = unlines $
-    [ ">>>>> " ++ show h ++ ":" ] ++ lns ++ ["<<<<<"]
+  show (File h lns) = T.unpack $ T.unlines $
+    [ ">>>>> " <> T.pack (show h) <> ":" ] ++ lns ++ ["<<<<<"]
 
 
 
@@ -62,7 +65,8 @@
   arbitrary = do
     Positive n <- arbitrary :: Gen (Positive Int)
     handles <- fmap nub $ vectorOf (n `mod` 20) arbitrary
-    FileSystem <$> mapM (\k -> File k <$> arbitrary ) handles
+    let contents = listOf (fmap T.pack arbitrary)
+    FileSystem <$> mapM (\k -> File k <$> contents ) handles
 
 -- | No files; populate with `writeLines` or `appendLines`.
 emptyFileSystem :: FileSystem a
@@ -102,7 +106,7 @@
 hasFile
   :: (Eq a)
   => a -- ^ Handle
-  -> [String] -- ^ Contents
+  -> [Text] -- ^ Contents
   -> FileSystem a
   -> Bool
 hasFile h lns fs = case getLines h fs of
@@ -114,14 +118,14 @@
   :: (Eq a)
   => a -- ^ Handle
   -> FileSystem a
-  -> Maybe [String]
+  -> Maybe [Text]
 getLines h = fmap _fileContents . getFile h
 
 -- | Overwrite the contents of a file.
 writeLines
   :: (Eq a)
   => a -- ^ Handle
-  -> [String] -- ^ Contents
+  -> [Text] -- ^ Contents
   -> FileSystem a
   -> FileSystem a
 writeLines a lns = putFile (File a lns)
@@ -130,7 +134,7 @@
 appendLines
   :: (Eq a)
   => a -- ^ Handle
-  -> [String] -- ^ Contents
+  -> [Text] -- ^ Contents
   -> FileSystem a
   -> FileSystem a
 appendLines h ls (FileSystem fs) = FileSystem $ appendLines' fs
@@ -138,7 +142,7 @@
     appendLines' zs = case zs of
       [] -> [File h ls]
       (File u ms):rest -> if u == h
-        then (File u (ms ++ ls)) : rest
+        then (File u (ms <> ls)) : rest
         else (File u ms) : appendLines' rest
 
 -- | Delete a file; if no such file exists, has no effect.
@@ -162,7 +166,7 @@
   -> e -- ^ EOF error
   -> a -- ^ Handle
   -> FileSystem a
-  -> Either e (String, FileSystem a)
+  -> Either e (Text, FileSystem a)
 readLine notFound eof k (FileSystem fs) = getline fs []
   where
     getline xs ys = case xs of
diff --git a/src/Network/HTTP/Client/Extras.hs b/src/Network/HTTP/Client/Extras.hs
--- a/src/Network/HTTP/Client/Extras.hs
+++ b/src/Network/HTTP/Client/Extras.hs
@@ -10,7 +10,7 @@
 HTTP helpers
 -}
 
-{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE RecordWildCards, CPP #-}
 module Network.HTTP.Client.Extras (
     Url
   , HttpResponse(..)
@@ -32,19 +32,35 @@
   ( ByteString, unpack )
 import Data.ByteString.Lazy
   ( ByteString, unpack )
+import Data.Text
+  ( Text )
 import Data.Vector
   ( fromList )
 import Network.HTTP.Client
   ( HttpException(..), CookieJar, HttpExceptionContent(StatusCodeException)
   , Response, responseCookieJar, responseBody, createCookieJar
   , responseHeaders, responseVersion, responseStatus )
+
+#if MIN_VERSION_http_client(0,7,0)
+-- http-client 0.7.0 removed the Eq instance for CookieJar in favor
+-- of multiple explicit equivalence relations.
+import Network.HTTP.Client
+  ( equalCookieJar )
+#endif
+
 import Network.HTTP.Types
 import Data.Aeson (Value(..), object, (.=))
+
+#if MIN_VERSION_aeson(2,0,0)
+-- aeson 2.0.0.0 introduced KeyMap over HashMap
+import Data.Aeson.Key (fromString)
+#endif
+
 import qualified Data.Text as T (Text, pack)
 
 
 -- | To make type signatures nicer
-type Url = String
+type Url = Text
 
 -- | Non-opaque HTTP response type.
 data HttpResponse = HttpResponse
@@ -53,8 +69,23 @@
   , _responseHeaders :: ResponseHeaders
   , _responseBody :: ByteString
   , _responseCookieJar :: CookieJar
-  } deriving (Eq, Show)
+  } deriving (Show)
 
+instance Eq HttpResponse where
+  r1 == r2 = and
+    [ (==)           (_responseStatus    r1) (_responseStatus    r2)
+    , (==)           (_responseVersion   r1) (_responseVersion   r2)
+    , (==)           (_responseHeaders   r1) (_responseHeaders   r2)
+    , (==)           (_responseBody      r1) (_responseBody      r2)
+
+#if MIN_VERSION_http_client(0,7,0)
+    , equalCookieJar (_responseCookieJar r1) (_responseCookieJar r2)
+#else
+    , (==)           (_responseCookieJar r1) (_responseCookieJar r2)
+#endif
+
+    ]
+
 -- | Convert an opaque `Response ByteString` into an `HttpResponse`.
 readHttpResponse :: Response ByteString -> HttpResponse
 readHttpResponse r = HttpResponse
@@ -72,7 +103,14 @@
 jsonResponseHeaders =
   Array . fromList . map (\(k,v) -> object [ (key k) .= (val v) ])
   where
-    key = T.pack . concatMap esc . show
+
+    key =
+#if MIN_VERSION_aeson(2,0,0)
+      fromString . concatMap esc . show
+#else
+      T.pack . concatMap esc . show
+#endif
+
     val = T.pack . concatMap esc . show
 
     esc c = case c of
diff --git a/test/Control/Monad/Script/Http/Test.hs b/test/Control/Monad/Script/Http/Test.hs
--- a/test/Control/Monad/Script/Http/Test.hs
+++ b/test/Control/Monad/Script/Http/Test.hs
@@ -11,13 +11,23 @@
 import Data.Proxy
 import Data.String
 import Data.ByteString.Lazy
-  ( ByteString, pack )
+  ( ByteString, pack, toStrict, fromChunks )
+import Data.Text
+  ( Text )
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as T
+import Data.Text.Extras
 import Data.Typeable
 import Data.List (isSuffixOf)
+import Network.HTTP.Types
+import Network.HTTP.Client
+  ( HttpException(..), HttpExceptionContent(..) )
 
 import Test.Tasty
 import Test.Tasty.HUnit
 import Test.Tasty.QuickCheck
+import Test.QuickCheck.Property
+  ( failed, rejected )
 
 import Control.Monad.Script.Http
 import Data.MockIO
@@ -183,7 +193,7 @@
   -> (forall u. P p u -> eff u)
   -> (forall e s w t. IdentityT eff (Either (E e) t, S s, W e w) -> IO (Either (E e) t, S s, W e w))
   -> HttpT e r w s p eff ()
-  -> s -> r -> String -> Property
+  -> s -> r -> Text -> Property
 prop_comment_value _ _ _ _ eval cond x s r msg =
   checkHttpTT (basicState s) (testEnv r) eval cond
     (hasValue (== ())) $
@@ -196,7 +206,7 @@
   -> (forall u. P p u -> eff u)
   -> (forall e s w t. IdentityT eff (Either (E e) t, S s, W e w) -> IO (Either (E e) t, S s, W e w))
   -> HttpT e r w s p eff ()
-  -> s -> r -> String -> Property
+  -> s -> r -> Text -> Property
 prop_comment_state _ _ _ _ eval cond x s r msg =
   checkHttpTT (basicState s) (testEnv r) eval cond
     (hasState (== s)) $
@@ -209,9 +219,9 @@
   -> (forall u. P p u -> eff u)
   -> (forall e s w t. IdentityT eff (Either (E e) t, S s, W e w) -> IO ((Either (E e) t, S s, W e w), MockWorld u))
   -> HttpT e r w s p eff ()
-  -> s -> r -> String -> Property
+  -> s -> r -> Text -> Property
 prop_comment_write _ _ _ _ eval cond x s r str =
-  let msg = filter (/= '\n') str in
+  let msg = T.filter (/= '\n') str in
   checkHttpTT (basicState s) (noisyEnv r) eval cond
     (hasWorld $ outputContains msg) $
     as x $ do
@@ -223,9 +233,9 @@
   -> (forall u. P p u -> eff u)
   -> (forall e s w t. IdentityT eff (Either (E e) t, S s, W e w) -> IO ((Either (E e) t, S s, W e w), MockWorld u))
   -> HttpT e r w s p eff ()
-  -> s -> r -> String -> Property
+  -> s -> r -> Text -> Property
 prop_comment_silent _ _ _ _ eval cond x s r str =
-  let msg = filter (/= '\n') str in
+  let msg = T.filter (/= '\n') str in
   checkHttpTT (basicState s) (testEnv r) eval cond
     (hasWorld outputIsEmpty) $
     as x $ do
@@ -238,13 +248,13 @@
 hasWorld p (_,w) = p w
 
 outputContains
-  :: String
+  :: Text
   -> MockWorld u
   -> Bool
 outputContains str world =
   case getLines (Right stdout) $ _files world of
     Nothing -> False
-    Just ls -> any (isSuffixOf str) ls
+    Just ls -> any (T.isSuffixOf str) ls
 
 outputIsEmpty
   :: MockWorld u -> Bool
@@ -286,7 +296,7 @@
   -> s -> r -> Int -> Property
 prop_wait_write _ _ _ _ eval cond x s r k =
   checkHttpTT (basicState s) (noisyEnv r) eval cond
-    (hasWorld $ outputContains $ "Wait for " ++ show k ++ "μs") $
+    (hasWorld $ outputContains $ "Wait for " <> T.pack (show k) <> "μs") $
     as x $ do
       wait k
 
@@ -308,14 +318,14 @@
   => Proxy e -> Proxy r -> Proxy w -> Proxy s
   -> (forall u. P p u -> eff u)
   -> (forall e s w t. IdentityT eff (Either (E e) t, S s, W e w) -> IO (Either (E e) t, S s, W e w))
-  -> HttpT e r w s p eff ()
+  -> HttpT e r w s p eff Int
   -> s -> r -> Property
 prop_httpGet _ _ _ _ eval cond x s r =
   checkHttpTT (basicState s) (testEnv r) eval cond
-    (hasValue (== ())) $
+    (hasValue (== 200)) $
     as x $ do
-      httpGet "http://example.com"
-      return ()
+      response <- httpGet "http://example.com"
+      return (statusCode $ _responseStatus response)
 
 prop_httpGet_json
   :: (Monad eff, Show e, Show w, Show s, Show u)
@@ -332,7 +342,7 @@
         >>= (return . _responseBody)
         >>= parseJson
       val2 <- parseJson "{\"key\":\"value\"}"
-      comment $ show $ val1 == val2
+      comment $ T.pack $ show $ val1 == val2
       return ()
 
 prop_httpGet_write
@@ -396,28 +406,30 @@
   => Proxy e -> Proxy r -> Proxy w -> Proxy s
   -> (forall u. P p u -> eff u)
   -> (forall e s w t. IdentityT eff (Either (E e) t, S s, W e w) -> IO (Either (E e) t, S s, W e w))
-  -> HttpT e r w s p eff ()
-  -> s -> r -> String -> Property
+  -> HttpT e r w s p eff Int
+  -> s -> r -> Text -> Property
 prop_httpPost _ _ _ _ eval cond x s r payload =
   checkHttpTT (basicState s) (testEnv r) eval cond
-    (hasValue (== ())) $
+    (hasValue (== 200)) $
     as x $ do
-      httpPost "http://example.com" (fromString payload)
-      return ()
+      response <- httpPost "http://example.com" $
+        fromChunks [T.encodeUtf8 payload]
+      return (statusCode $ _responseStatus response)
 
 prop_httpSilentPost
   :: (Monad eff, Show e, Show w, Show s)
   => Proxy e -> Proxy r -> Proxy w -> Proxy s
   -> (forall u. P p u -> eff u)
   -> (forall e s w t. IdentityT eff (Either (E e) t, S s, W e w) -> IO (Either (E e) t, S s, W e w))
-  -> HttpT e r w s p eff ()
-  -> s -> r -> String -> Property
+  -> HttpT e r w s p eff Int
+  -> s -> r -> Text -> Property
 prop_httpSilentPost _ _ _ _ eval cond x s r payload =
   checkHttpTT (basicState s) (testEnv r) eval cond
-    (hasValue (== ())) $
+    (hasValue (== 200)) $
     as x $ do
-      httpSilentPost "http://example.com" (fromString payload)
-      return ()
+      response <- httpSilentPost "http://example.com" $
+        fromChunks [T.encodeUtf8 payload]
+      return (statusCode $ _responseStatus response)
 
 prop_throwError
   :: (Monad eff, Eq e, Show e, Show w, Show s)
@@ -521,12 +533,21 @@
       lookupKeyJson "key" obj >>= constructFromJson
 
 hasValue
-  :: (t -> Bool)
+  :: (Show e) => (t -> Bool)
   -> (Either (E e) t, S s, W e w)
-  -> Bool
+  -> Property
 hasValue p (x,_,_) = case x of
-  Left e -> False
-  Right a -> p a
+  Left (E_Http (HttpExceptionRequest _ err)) ->
+    case err of
+      -- These errors are outside the control of this code, so it's better
+      -- to reject cases that trigger them than to fail the whole suite.
+      ConnectionTimeout -> property rejected
+      InternalException _ -> property rejected
+
+      -- These might be real errors. :)
+      e -> counterexample (show e) failed
+  Left e -> counterexample (show e) failed
+  Right a -> property $ p a
 
 hasLog
   :: (W e w -> Bool)
diff --git a/test/Data/MockIO/FileSystem/Test.hs b/test/Data/MockIO/FileSystem/Test.hs
--- a/test/Data/MockIO/FileSystem/Test.hs
+++ b/test/Data/MockIO/FileSystem/Test.hs
@@ -4,10 +4,14 @@
 
 import Data.Proxy
   ( Proxy(..) )
+import Data.Text
+  ( Text )
 
 import Test.Tasty
 import Test.Tasty.QuickCheck
 
+import Data.Text.Extras
+
 import Data.MockIO.FileSystem
 
 tests :: Int -> TestTree
@@ -66,31 +70,31 @@
   if (x == y) && (y == z) then x == z else True
 
 prop_appendLines_not_equal
-  :: (Eq a) => Proxy a -> a -> String -> FileSystem a -> Bool
+  :: (Eq a) => Proxy a -> a -> Text -> FileSystem a -> Bool
 prop_appendLines_not_equal _ h lns fs =
   fs /= appendLines h [lns] fs
 
 prop_writeLines_lzero
-  :: (Eq a) => Proxy a -> a -> [String] -> [String] -> FileSystem a -> Bool
+  :: (Eq a) => Proxy a -> a -> [Text] -> [Text] -> FileSystem a -> Bool
 prop_writeLines_lzero _ h ls ms fs =
   (writeLines h ls fs) == (writeLines h ls $ writeLines h ms fs)
 
 prop_writeLines_fileExists
-  :: (Eq a) => Proxy a -> a -> [String] -> FileSystem a -> Bool
+  :: (Eq a) => Proxy a -> a -> [Text] -> FileSystem a -> Bool
 prop_writeLines_fileExists _ h lns fs =
   fileExists h $ writeLines h lns fs
 
 prop_hasFile_writeLines
-  :: (Eq a) => Proxy a -> a -> [String] -> FileSystem a -> Bool
+  :: (Eq a) => Proxy a -> a -> [Text] -> FileSystem a -> Bool
 prop_hasFile_writeLines _ h lns fs =
   hasFile h lns $ writeLines h lns fs
 
 prop_appendLines_writeLines
-  :: (Eq a) => Proxy a -> a -> [String] -> [String] -> FileSystem a -> Bool
+  :: (Eq a) => Proxy a -> a -> [Text] -> [Text] -> FileSystem a -> Bool
 prop_appendLines_writeLines _ h as bs fs =
   (==)
     (appendLines h bs $ writeLines h as fs)
-    (writeLines h (as ++ bs) fs)
+    (writeLines h (as <> bs) fs)
 
 prop_deleteFile_idempotent
   :: (Eq a) => Proxy a -> a -> FileSystem a -> Bool
@@ -120,7 +124,7 @@
     Just ms -> hasFile h ms fs
 
 prop_readLine_writeLines
-  :: (Eq a) => Proxy a -> a -> String -> FileSystem a -> Bool
+  :: (Eq a) => Proxy a -> a -> Text -> FileSystem a -> Bool
 prop_readLine_writeLines _ h str fs =
   case readLine () () h $ writeLines h [str] fs of
     Left _ -> False
diff --git a/test/Data/Text/Extras.hs b/test/Data/Text/Extras.hs
new file mode 100644
--- /dev/null
+++ b/test/Data/Text/Extras.hs
@@ -0,0 +1,7 @@
+module Data.Text.Extras where
+
+import Data.Text
+import Test.QuickCheck
+
+instance Arbitrary Text where
+  arbitrary = pack <$> arbitrary
