diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
 # Revision history for tasty-checklist
 
+## 1.0.3.0 -- 2021-08-13
+  * Bump doctest upper constraints (contributed by Felix Yan)
+  * Added missing doctest source
+  * Added `Observe` to `DerivedVal` to allow using a user-supplied
+    observation function on failure.
+  * Added `multiLineDiff` helper function
+
 ## 1.0.2.0 -- 2021-07-25
   * Added `Got` specifier for simpler specification of boolean
     `DerivedVal` than using `Val`.
diff --git a/src/Test/Tasty/Checklist.hs b/src/Test/Tasty/Checklist.hs
--- a/src/Test/Tasty/Checklist.hs
+++ b/src/Test/Tasty/Checklist.hs
@@ -46,13 +46,14 @@
   -- $checkValues
   -- $setup
   , checkValues
-  , DerivedVal(Val, Got)
+  , DerivedVal(Val, Got, Observe)
   -- * Error reporting
   , CheckResult
   , ChecklistFailures
   -- * Displaying tested values
   , TestShow(testShow)
   , testShowList
+  , multiLineDiff
   )
 where
 
@@ -158,10 +159,14 @@
 
 check :: (CanCheck, TestShow a, MonadIO m)
       => Text -> (a -> Bool) -> a -> m ()
-check what eval val = do
+check = checkShow testShow
+
+checkShow :: (CanCheck, MonadIO m)
+          => (a -> String) -> Text -> (a -> Bool) -> a -> m ()
+checkShow showit what eval val = do
   r <- liftIO $ evaluate (eval val)
   unless r $ do
-    let chk = CheckFailed what $ T.pack $ testShow val
+    let chk = CheckFailed what $ T.pack $ showit val
     liftIO $ modifyIORef ?checker (chk:)
 
 
@@ -279,6 +284,10 @@
         ti = T.pack (testShow got)
         tv = T.pack (testShow v)
     in check msg (v ==) r
+  (Observe txt fld v observationReport) ->
+    let r = fld got
+        msg = txt <> " observation failure"
+    in checkShow (observationReport v) msg (v ==) r
   (Got txt fld) ->
     let r = fld got
         msg = txt <> " on input <<" <> ti <> ">>"
@@ -287,6 +296,9 @@
 
 -- | Each entry in the 'Data.Parameterized.Context.Assignment' list
 -- for 'checkValues' should be one of these 'DerivedVal' values.
+--
+-- The @i@ type parameter is the input type, and the @d@ is the value
+-- derived from that input type.
 
 data DerivedVal i d where
 
@@ -296,7 +308,7 @@
   -- not obtained.
   Val :: (TestShow d, Eq d) => Text -> (i -> d) -> d -> DerivedVal i d
 
-  -- | Got allow specification of a description string and an
+  -- | Got allows specification of a description string and an
   -- extraction function.  The 'checkValues' function will add a
   -- Failure if the extraction result is False.
   --
@@ -304,12 +316,22 @@
   --
   Got :: Text -> (i -> Bool) -> DerivedVal i Bool
 
+  -- | Observe performs the same checking as Val except the TestShow
+  -- information for the actual and expected values are not as useful
+  -- (e.g. they are lengthy, multi-line, or gibberish) so instead this
+  -- allows the specification of a function that will take the
+  -- supplied expected value and the result of the extraction function
+  -- (the actual), respectively, and generate its own description of
+  -- the failure.
+  --
+  Observe :: (Eq d) => Text -> (i -> d) -> d -> (d -> d -> String) -> DerivedVal i d
+
 ----------------------------------------------------------------------
 
 -- | The 'TestShow' class is defined to provide a way for the various
 -- data objects tested by this module to be displayed when tests fail.
 -- The default 'testShow' will use a 'Show' instance, but this can be
--- overridden if there are alternate ways t o display a particular
+-- overridden if there are alternate ways to display a particular
 -- object (e.g. pretty-printing, etc.)
 
 class TestShow v where
@@ -337,3 +359,37 @@
 
 testShowList :: TestShow v => [v] -> String
 testShowList  l = "[ " <> (List.intercalate ", " (testShow <$> l)) <> " ]"
+
+
+-- | The multiLineDiff is another helper function that can be used to
+-- format a line-by-line difference display of two Text
+-- representations.  This is provided as a convenience function to
+-- help format large text regions for easier comparison.
+
+multiLineDiff :: T.Text -> T.Text -> String
+multiLineDiff expected actual =
+  let dl (e,a) = if e == a then db e else de " ↱" e <> "\n    " <> da " ↳" a
+      db b = "|        > " <> b
+      de m e = "|" <> m <> "expect> " <> e
+      da m a = "|" <> m <> "actual> " <> a
+      el = T.lines expected
+      al = T.lines actual
+      addnum :: Int -> T.Text -> T.Text
+      addnum n l = let nt = T.pack (show n)
+                       nl = T.length nt
+                   in T.take (4 - nl) "    " <> nt <> l
+      banner = "MISMATCH between "
+               <> T.pack (show $ length el) <> " expected and "
+               <> T.pack (show $ length al) <> " actual"
+      diffReport = fmap (uncurry addnum) $
+                   zip [1..] $ concat $
+                   -- Highly simplistic "diff" output assumes
+                   -- correlated lines: added or removed lines just
+                   -- cause everything to shown as different from that
+                   -- point forward.
+                   [ fmap dl $ zip el al
+                   , fmap (de "∌ ") $ drop (length al) el
+                   , fmap (da "∹ ") $ drop (length el) al
+                   ]
+      details = banner : diffReport
+  in if expected == actual then "<no difference>" else T.unpack (T.unlines details)
diff --git a/tasty-checklist.cabal b/tasty-checklist.cabal
--- a/tasty-checklist.cabal
+++ b/tasty-checklist.cabal
@@ -1,6 +1,6 @@
 cabal-version:      2.2
 name:               tasty-checklist
-version:            1.0.2.0
+version:            1.0.3.0
 synopsis:           Check multiple items during a tasty test
 description:
    Allows the test to check a number of items during a test and
@@ -69,7 +69,7 @@
   main-is:          DocTestChecklist.hs
   build-depends: base
                , HUnit
-               , doctest >= 0.10 && < 0.17
+               , doctest >= 0.10 && < 0.19
                , parameterized-utils
                , tasty
                , tasty-checklist
diff --git a/test/TestChecklist.hs b/test/TestChecklist.hs
--- a/test/TestChecklist.hs
+++ b/test/TestChecklist.hs
@@ -1,13 +1,16 @@
+{-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE PatternSynonyms #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
 
 module Main where
 
 import Data.Parameterized.Context ( pattern Empty, pattern (:>) )
+import qualified Data.Text as T
 import Test.Tasty
+import Test.Tasty.Checklist
 import Test.Tasty.ExpectedFailure
 import Test.Tasty.HUnit
-import Test.Tasty.Checklist
 
 
 main :: IO ()
@@ -112,6 +115,32 @@
           :> Val "the answer" answer 19
          )
 
+       ----------------------------------------
+
+       , testCase "Compare identical multi-line results" $
+         withChecklist "multi-line" $
+         let result = fmap (T.replace " " " ") soliloquy
+         in result `checkValues`
+            (Empty
+             :> Val "length" length (length soliloquy)
+             :> Val "word count" (length . T.words . T.unlines) 114
+             :> Observe "unchanged" id soliloquy
+              (\expect actual ->
+                 multiLineDiff (T.unlines expect) (T.unlines actual))
+            )
+
+       , expectFailBecause "there are changes" $
+         testCase "Compare changed multi-line results" $
+         withChecklist "multi-line" $
+         let result = fmap (T.replace "  " " ") soliloquy
+         in result `checkValues`
+            (Empty
+             :> Val "length" length (length soliloquy)
+             :> Val "word count" (length . T.words . T.unlines) 113
+             :> Observe "unchanged" id soliloquy
+              (\expect actual ->
+                 multiLineDiff (T.unlines expect) (T.unlines actual))
+            )
        ]
 
 ----------------------------------------------------------------------
@@ -151,3 +180,26 @@
 
 instance TestShow Opaque where
   testShow = display
+
+
+----------------------------------------------------------------------
+
+soliloquy :: [T.Text]
+soliloquy =
+  [ "To be, or not to be--that is the question:"
+  , "Whether 'tis nobler in the mind to suffer"
+  , "The slings and arrows of outrageous fortune"
+  , "Or to take arms against a sea of troubles"
+  , "And by opposing end them.  To die, to sleep--"
+  , "No more--and by a sleep to say we end"
+  , "The heartache, and the thousand natural shocks"
+  , "That flesh is heir to. 'Tis a consummation"
+  , "Devoutly to be wished.  To die, to sleep--"
+  , "To sleep--perchance to dream: ay, there's the rub,"
+  , "For in that sleep of death what dreams may come"
+  , "When we have shuffled off this mortal coil,"
+  , "Must give us pause.  There's the respect"
+  , "That makes calamity of so long life."
+  ]
+
+instance TestShow [T.Text] where testShow = show . T.unlines
