diff --git a/hspec-discover/example/Spec.hs b/hspec-discover/example/Spec.hs
deleted file mode 100644
--- a/hspec-discover/example/Spec.hs
+++ /dev/null
@@ -1,1 +0,0 @@
-{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
diff --git a/hspec-discover/integration-test/empty/Spec.hs b/hspec-discover/integration-test/empty/Spec.hs
deleted file mode 100644
--- a/hspec-discover/integration-test/empty/Spec.hs
+++ /dev/null
@@ -1,1 +0,0 @@
-{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
diff --git a/hspec-discover/test-data/empty-dir/Foo/Bar/Baz/.placeholder b/hspec-discover/test-data/empty-dir/Foo/Bar/Baz/.placeholder
new file mode 100644
--- /dev/null
+++ b/hspec-discover/test-data/empty-dir/Foo/Bar/Baz/.placeholder
diff --git a/hspec-discover/test-data/nested-spec/Foo/Bar/BazSpec.hs b/hspec-discover/test-data/nested-spec/Foo/Bar/BazSpec.hs
new file mode 100644
--- /dev/null
+++ b/hspec-discover/test-data/nested-spec/Foo/Bar/BazSpec.hs
diff --git a/hspec-discover/test-data/nested-spec/Foo/BarSpec.hs b/hspec-discover/test-data/nested-spec/Foo/BarSpec.hs
new file mode 100644
--- /dev/null
+++ b/hspec-discover/test-data/nested-spec/Foo/BarSpec.hs
diff --git a/hspec-discover/test-data/nested-spec/FooSpec.hs b/hspec-discover/test-data/nested-spec/FooSpec.hs
new file mode 100644
--- /dev/null
+++ b/hspec-discover/test-data/nested-spec/FooSpec.hs
diff --git a/hspec-discover/test-data/no-intermediate-specs/Foo/Bar/BazSpec.hs b/hspec-discover/test-data/no-intermediate-specs/Foo/Bar/BazSpec.hs
new file mode 100644
--- /dev/null
+++ b/hspec-discover/test-data/no-intermediate-specs/Foo/Bar/BazSpec.hs
diff --git a/hspec-discover/test-data/prefix-name/Foo/BazSpec.hs b/hspec-discover/test-data/prefix-name/Foo/BazSpec.hs
new file mode 100644
--- /dev/null
+++ b/hspec-discover/test-data/prefix-name/Foo/BazSpec.hs
diff --git a/hspec-discover/test-data/prefix-name/FooBar/BazSpec.hs b/hspec-discover/test-data/prefix-name/FooBar/BazSpec.hs
new file mode 100644
--- /dev/null
+++ b/hspec-discover/test-data/prefix-name/FooBar/BazSpec.hs
diff --git a/hspec-discover/test-data/prefix-name/FooBarSpec.hs b/hspec-discover/test-data/prefix-name/FooBarSpec.hs
new file mode 100644
--- /dev/null
+++ b/hspec-discover/test-data/prefix-name/FooBarSpec.hs
diff --git a/hspec-discover/test-data/prefix-name/FooSpec.hs b/hspec-discover/test-data/prefix-name/FooSpec.hs
new file mode 100644
--- /dev/null
+++ b/hspec-discover/test-data/prefix-name/FooSpec.hs
diff --git a/hspec-discover/test-data/several-specs/BarSpec.hs b/hspec-discover/test-data/several-specs/BarSpec.hs
new file mode 100644
--- /dev/null
+++ b/hspec-discover/test-data/several-specs/BarSpec.hs
diff --git a/hspec-discover/test-data/several-specs/BazSpec.hs b/hspec-discover/test-data/several-specs/BazSpec.hs
new file mode 100644
--- /dev/null
+++ b/hspec-discover/test-data/several-specs/BazSpec.hs
diff --git a/hspec-discover/test-data/several-specs/FooSpec.hs b/hspec-discover/test-data/several-specs/FooSpec.hs
new file mode 100644
--- /dev/null
+++ b/hspec-discover/test-data/several-specs/FooSpec.hs
diff --git a/hspec-discover/test-data/single-spec-nested/Foo/BarSpec.hs b/hspec-discover/test-data/single-spec-nested/Foo/BarSpec.hs
new file mode 100644
--- /dev/null
+++ b/hspec-discover/test-data/single-spec-nested/Foo/BarSpec.hs
diff --git a/hspec-discover/test-data/single-spec/FooSpec.hs b/hspec-discover/test-data/single-spec/FooSpec.hs
new file mode 100644
--- /dev/null
+++ b/hspec-discover/test-data/single-spec/FooSpec.hs
diff --git a/hspec-discover/test-data/single-spec/Spec.hs b/hspec-discover/test-data/single-spec/Spec.hs
new file mode 100644
--- /dev/null
+++ b/hspec-discover/test-data/single-spec/Spec.hs
diff --git a/hspec-discover/test/RunSpec.hs b/hspec-discover/test/RunSpec.hs
new file mode 100644
--- /dev/null
+++ b/hspec-discover/test/RunSpec.hs
@@ -0,0 +1,90 @@
+{-# LANGUAGE OverloadedStrings #-}
+module RunSpec (main, spec) where
+
+import           Test.Hspec.Meta
+
+import           Run
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec = do
+  describe "findSpecs" $ do
+    context "when specs are not nested" $ do
+      it "finds a single spec" $ do
+        findSpecs "hspec-discover/test-data/single-spec/Spec.hs" `shouldReturn` [SpecNode "Foo" True []]
+
+      it "finds several specs" $ do
+        findSpecs "hspec-discover/test-data/several-specs/Spec.hs" `shouldReturn` [SpecNode "Bar" True [], SpecNode "Baz" True [], SpecNode "Foo" True []]
+
+    context "when specs are nested" $ do
+      it "finds a single spec" $ do
+        findSpecs "hspec-discover/test-data/single-spec-nested/Spec.hs" `shouldReturn` [SpecNode "Foo" False [SpecNode "Bar" True []]]
+
+      it "properly groups nested specs" $ do
+        findSpecs "hspec-discover/test-data/nested-spec/Spec.hs" `shouldReturn` [SpecNode "Foo" True [SpecNode "Bar" True [SpecNode "Baz" True []]]]
+
+    context "given a nested spec, without specs at the intermediate nodes" $ do
+      it "finds a single spec" $ do
+        findSpecs "hspec-discover/test-data/no-intermediate-specs/Spec.hs" `shouldReturn` [SpecNode "Foo" False [SpecNode "Bar" False [SpecNode "Baz" True []]]]
+
+    context "given a nested specs, with specs at the intermediate nodes" $ do
+      context "with two top-level specs, where one spec name is a prefix of the other" $ do
+        it "properly sorts specs" $ do
+          findSpecs "hspec-discover/test-data/prefix-name/Spec.hs" `shouldReturn`
+            [SpecNode "Foo" True [SpecNode "Baz" True []], SpecNode "FooBar" True [SpecNode "Baz" True []]]
+
+    context "when there are no specs" $ do
+      it "returns an empty list" $ do
+        findSpecs "hspec-discover/test-data/empty-dir/Spec.hs" `shouldReturn` []
+
+  describe "formatSpec" $ do
+    it "generates code for a spec" $
+      formatSpec (SpecNode "Foo" True []) "" `shouldBe` "describe \"Foo\" FooSpec.spec"
+
+    it "generates code for a nested spec" $
+      formatSpec (SpecNode "Foo" True [SpecNode "Bar" True [SpecNode "Baz" True []]]) "" `shouldBe`
+        "describe \"Foo\" FooSpec.spec >> describe \"Foo.Bar\" Foo.BarSpec.spec >> describe \"Foo.Bar.Baz\" Foo.Bar.BazSpec.spec"
+
+  describe "formatSpecs" $ do
+    it "generates code for a list of specs" $ do
+      formatSpecs [SpecNode "Bar" True [], SpecNode "Baz" True [], SpecNode "Foo" True []] "" `shouldBe`
+        "describe \"Bar\" BarSpec.spec >> describe \"Baz\" BazSpec.spec >> describe \"Foo\" FooSpec.spec"
+
+    it "generates code for an empty list" $ do
+      formatSpecs [] "" `shouldBe`
+        "return ()"
+
+  describe "formatSpecNested" $ do
+    it "generates code for a spec" $
+      formatSpecNested (SpecNode "Foo" True []) "" `shouldBe` "describe \"Foo\" (FooSpec.spec)"
+
+    it "generates code for a nested spec" $
+      formatSpecNested (SpecNode "Foo" True [SpecNode "Bar" True [SpecNode "Baz" True []]]) "" `shouldBe`
+        "describe \"Foo\" (FooSpec.spec >> describe \"Bar\" (Foo.BarSpec.spec >> describe \"Baz\" (Foo.Bar.BazSpec.spec)))"
+
+  describe "formatSpecsNested" $ do
+    it "generates code for a list of specs" $ do
+      formatSpecsNested [SpecNode "Bar" True [], SpecNode "Baz" True [], SpecNode "Foo" True []] "" `shouldBe`
+        "describe \"Bar\" (BarSpec.spec) >> describe \"Baz\" (BazSpec.spec) >> describe \"Foo\" (FooSpec.spec)"
+
+    it "generates code for an empty list" $ do
+      formatSpecsNested [] "" `shouldBe`
+        "return ()"
+
+  describe "importList" $ do
+    it "generates imports for a spec" $ do
+      importList [SpecNode "Foo" True []] "" `shouldBe` "import qualified FooSpec\n"
+
+    it "generates imports for a nested spec" $
+      importList [SpecNode "Foo" True [SpecNode "Bar" True [SpecNode "Baz" True []]]] "" `shouldBe`
+        "import qualified FooSpec\nimport qualified Foo.BarSpec\nimport qualified Foo.Bar.BazSpec\n"
+
+    it "generates imports for a list of specs" $ do
+      importList [SpecNode "Bar" True [], SpecNode "Baz" True [], SpecNode "Foo" True []] "" `shouldBe`
+        "import qualified BarSpec\nimport qualified BazSpec\nimport qualified FooSpec\n"
+
+    it "generates imports for a nested spec that has no specs at the intermediate nodes" $ do
+      importList [SpecNode "Foo" False [SpecNode "Bar" False [SpecNode "Baz" True []]]] "" `shouldBe`
+        "import qualified Foo.Bar.BazSpec\n"
diff --git a/hspec-discover/test/Spec.hs b/hspec-discover/test/Spec.hs
--- a/hspec-discover/test/Spec.hs
+++ b/hspec-discover/test/Spec.hs
@@ -1,1 +1,5 @@
-{-# OPTIONS_GHC -F -pgmF hspec-meta-discover #-}
+{-# LINE 1 "hspec-discover/test/Spec.hs" #-}module Main where
+import Test.Hspec
+import qualified RunSpec
+main :: IO ()
+main = hspec $ describe "Run" RunSpec.spec
diff --git a/hspec-discover/test/Test/Hspec/Meta.hs b/hspec-discover/test/Test/Hspec/Meta.hs
new file mode 100644
--- /dev/null
+++ b/hspec-discover/test/Test/Hspec/Meta.hs
@@ -0,0 +1,2 @@
+module Test.Hspec.Meta (module Test.Hspec) where
+import Test.Hspec
diff --git a/hspec.cabal b/hspec.cabal
--- a/hspec.cabal
+++ b/hspec.cabal
@@ -1,5 +1,5 @@
 name:             hspec
-version:          1.4.2
+version:          1.4.2.1
 license:          BSD3
 license-file:     LICENSE
 copyright:        (c) 2011-2012 Trystan Spangler, (c) 2011-2012 Simon Hengel, (c) 2011 Greg Weber
@@ -20,6 +20,24 @@
                   .
                   The Hspec Manual is at <http://hspec.github.com/>.
 
+-- find hspec-discover/test-data/ -type f
+extra-source-files:
+  hspec-discover/test-data/single-spec/FooSpec.hs
+  hspec-discover/test-data/single-spec/Spec.hs
+  hspec-discover/test-data/single-spec-nested/Foo/BarSpec.hs
+  hspec-discover/test-data/nested-spec/FooSpec.hs
+  hspec-discover/test-data/nested-spec/Foo/Bar/BazSpec.hs
+  hspec-discover/test-data/nested-spec/Foo/BarSpec.hs
+  hspec-discover/test-data/several-specs/BarSpec.hs
+  hspec-discover/test-data/several-specs/FooSpec.hs
+  hspec-discover/test-data/several-specs/BazSpec.hs
+  hspec-discover/test-data/empty-dir/Foo/Bar/Baz/.placeholder
+  hspec-discover/test-data/prefix-name/FooSpec.hs
+  hspec-discover/test-data/prefix-name/Foo/BazSpec.hs
+  hspec-discover/test-data/prefix-name/FooBarSpec.hs
+  hspec-discover/test-data/prefix-name/FooBar/BazSpec.hs
+  hspec-discover/test-data/no-intermediate-specs/Foo/Bar/BazSpec.hs
+
 source-repository head
   type: git
   location: https://github.com/hspec/hspec
@@ -63,6 +81,20 @@
       src, test
   main-is:
       Spec.hs
+  other-modules:
+      Mock
+      Util
+      Test.Hspec.Meta
+      -- actual spec files
+      Test.HspecSpec
+      Test.Hspec.CompatSpec
+      Test.Hspec.Core.TypeSpec
+      Test.Hspec.FailureReportSpec
+      Test.Hspec.FormattersSpec
+      Test.Hspec.HUnitSpec
+      Test.Hspec.QuickCheckSpec
+      Test.Hspec.RunnerSpec
+      Test.Hspec.UtilSpec
   ghc-options:
       -Wall -Werror
   build-depends:
@@ -76,7 +108,6 @@
     , QuickCheck >= 2.4.0.1 && < 2.6
     , hspec-expectations
 
-    , hspec-meta
     , process
     , ghc-paths
 
@@ -132,35 +163,12 @@
     , hspec-discover/test
   main-is:
       Spec.hs
+  other-modules:
+      Test.Hspec.Meta
+      -- actual spec files
+      RunSpec
   build-depends:
       base >= 4 && <= 5
     , filepath
     , directory
-    , hspec-meta
-
-test-suite hspec-discover-example
-  type:
-      exitcode-stdio-1.0
-  ghc-options:
-      -Wall -Werror
-  hs-source-dirs:
-      hspec-discover/example
-  main-is:
-      Spec.hs
-  build-depends:
-      base >= 4 && <= 5
-    , hspec
-    , QuickCheck
-
-test-suite hspec-discover-integration-test-empty
-  type:
-      exitcode-stdio-1.0
-  ghc-options:
-      -Wall -Werror
-  hs-source-dirs:
-      hspec-discover/integration-test/empty
-  main-is:
-      Spec.hs
-  build-depends:
-      base >= 4 && <= 5
     , hspec
diff --git a/test/Mock.hs b/test/Mock.hs
new file mode 100644
--- /dev/null
+++ b/test/Mock.hs
@@ -0,0 +1,15 @@
+module Mock where
+
+import           Control.Applicative
+import           Data.IORef
+
+newtype Mock = Mock (IORef Int)
+
+newMock :: IO Mock
+newMock = Mock <$> newIORef 0
+
+mockAction :: Mock -> IO ()
+mockAction (Mock ref) = modifyIORef ref succ
+
+mockCounter  :: Mock -> IO Int
+mockCounter  (Mock ref) = readIORef ref
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -1,1 +1,13 @@
-{-# OPTIONS_GHC -F -pgmF hspec-meta-discover #-}
+{-# LINE 1 "test/Spec.hs" #-}module Main where
+import Test.Hspec
+import qualified Test.HspecSpec
+import qualified Test.Hspec.CompatSpec
+import qualified Test.Hspec.Core.TypeSpec
+import qualified Test.Hspec.FailureReportSpec
+import qualified Test.Hspec.FormattersSpec
+import qualified Test.Hspec.HUnitSpec
+import qualified Test.Hspec.QuickCheckSpec
+import qualified Test.Hspec.RunnerSpec
+import qualified Test.Hspec.UtilSpec
+main :: IO ()
+main = hspec $ describe "Test.Hspec" Test.HspecSpec.spec >> describe "Test.Hspec.Compat" Test.Hspec.CompatSpec.spec >> describe "Test.Hspec.Core.Type" Test.Hspec.Core.TypeSpec.spec >> describe "Test.Hspec.FailureReport" Test.Hspec.FailureReportSpec.spec >> describe "Test.Hspec.Formatters" Test.Hspec.FormattersSpec.spec >> describe "Test.Hspec.HUnit" Test.Hspec.HUnitSpec.spec >> describe "Test.Hspec.QuickCheck" Test.Hspec.QuickCheckSpec.spec >> describe "Test.Hspec.Runner" Test.Hspec.RunnerSpec.spec >> describe "Test.Hspec.Util" Test.Hspec.UtilSpec.spec
diff --git a/test/Test/Hspec/CompatSpec.hs b/test/Test/Hspec/CompatSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Hspec/CompatSpec.hs
@@ -0,0 +1,19 @@
+{-# LANGUAGE DeriveDataTypeable #-}
+module Test.Hspec.CompatSpec (main, spec) where
+
+import           Test.Hspec.Meta
+
+import           Test.Hspec.Compat
+import           Data.Typeable
+
+data SomeType = SomeType
+  deriving Typeable
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec = do
+  describe "showType" $ do
+    it "shows fully qualified name of type" $ do
+      showType SomeType `shouldBe` "Test.Hspec.CompatSpec.SomeType"
diff --git a/test/Test/Hspec/Core/TypeSpec.hs b/test/Test/Hspec/Core/TypeSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Hspec/Core/TypeSpec.hs
@@ -0,0 +1,65 @@
+module Test.Hspec.Core.TypeSpec (main, spec) where
+
+import           Test.Hspec.Meta
+import           Test.QuickCheck
+
+import           Test.QuickCheck.Property (morallyDubiousIOProperty)
+import           Control.Exception (AsyncException(..), throwIO)
+import qualified Test.Hspec.Core.Type as H
+import qualified Test.Hspec.Pending as H (pending)
+
+main :: IO ()
+main = hspec spec
+
+evaluateExample :: H.Example e => e -> IO H.Result
+evaluateExample = H.evaluateExample H.defaultParams
+
+spec :: Spec
+spec = do
+  describe "evaluateExample" $ do
+    context "for Bool" $ do
+      it "returns Success on True" $ do
+        evaluateExample True `shouldReturn` H.Success
+
+      it "returns Fail on False" $ do
+        evaluateExample False `shouldReturn` H.Fail ""
+
+      it "propagates exceptions" $ do
+        evaluateExample (error "foobar" :: Bool) `shouldThrow` errorCall "foobar"
+
+    context "for Expectation" $ do
+      it "returns Success if all expectations hold" $ do
+        evaluateExample (23 `shouldBe` (23 :: Int)) `shouldReturn` H.Success
+
+      it "returns Fail if an expectation does not hold" $ do
+        evaluateExample (23 `shouldBe` (42 :: Int)) `shouldReturn` H.Fail "expected: 42\n but got: 23"
+
+      it "propagates exceptions" $ do
+        evaluateExample (error "foobar" :: Expectation) `shouldThrow` errorCall "foobar"
+
+    context "for Property" $ do
+      it "returns Success if property holds" $ do
+        evaluateExample (property $ \n -> n == (n :: Int)) `shouldReturn` H.Success
+
+      it "returns Fail if property does not hold" $ do
+        H.Fail _ <- evaluateExample $ property $ \n -> n /= (n :: Int)
+        return ()
+
+      it "shows what falsified it" $ do
+        H.Fail r <- evaluateExample $ property $ \ n -> n == (n + 1 :: Int)
+        lines r `shouldSatisfy` any (== "0")
+
+      it "propagates UserInterrupt" $ do
+        let p = morallyDubiousIOProperty (throwIO UserInterrupt >> return True)
+        evaluateExample p `shouldThrow` (== UserInterrupt)
+
+      it "propagates exceptions" $ do
+        pending "this probaly needs a patch to QuickCheck"
+        -- evaluateExample (property $ (error "foobar" :: Int -> Bool)) `shouldThrow` errorCall "foobar"
+
+    context "for pending" $ do
+      it "returns Pending" $ do
+        evaluateExample (H.pending) `shouldReturn` H.Pending Nothing
+
+      it "includes the optional reason" $ do
+        evaluateExample (H.pending "foo") `shouldReturn` H.Pending (Just "foo")
diff --git a/test/Test/Hspec/FailureReportSpec.hs b/test/Test/Hspec/FailureReportSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Hspec/FailureReportSpec.hs
@@ -0,0 +1,40 @@
+module Test.Hspec.FailureReportSpec (main, spec) where
+
+import           Test.Hspec.Meta
+
+import           System.IO
+import           System.IO.Silently
+import           Test.Hspec.FailureReport
+import           GHC.Paths (ghc)
+import           System.Process
+import           System.Exit
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec = do
+  describe "writeFailureReport" $ do
+    it "prints a warning on unexpected exceptions" $ do
+      (r, ()) <- hCapture [stderr] $ writeFailureReport (error "some error")
+      r `shouldBe` "WARNING: Could not write environment variable HSPEC_FAILURES (some error)\n"
+
+  -- GHCi needs to keep the environment on :reload, so that we can store
+  -- failures there.  Otherwise --re-run would not be very useful.  So we add a
+  -- test for that.
+  describe "GHCi" $ do
+    it "keeps environment variables on :reload" $ do
+      let flags = ["-v0", "--interactive", "-ignore-dot-ghci"]
+      (Just hIn, Just hOut, Nothing, processHandle) <- createProcess $ (proc ghc flags) {
+          std_in = CreatePipe
+        , std_out = CreatePipe
+        }
+      hPutStrLn hIn "import System.SetEnv"
+      hPutStrLn hIn "setEnv \"FOO\" \"bar\""
+      hPutStrLn hIn ":reload"
+      hPutStrLn hIn "import System.Environment"
+      hPutStrLn hIn "getEnv \"FOO\""
+      hClose hIn
+      r <- hGetContents hOut
+      length r `seq` r `shouldBe` "\"bar\"\n"
+      waitForProcess processHandle `shouldReturn` ExitSuccess
diff --git a/test/Test/Hspec/FormattersSpec.hs b/test/Test/Hspec/FormattersSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Hspec/FormattersSpec.hs
@@ -0,0 +1,225 @@
+{-# LANGUAGE CPP #-}
+module Test.Hspec.FormattersSpec (main, spec) where
+
+import           Test.Hspec.Meta
+
+import           Util
+import           System.IO.Silently (capture)
+import qualified Test.Hspec as H
+import qualified Test.Hspec.Core as H (Result(..))
+import qualified Test.Hspec.Runner as H
+import qualified Test.Hspec.Formatters as H
+
+#ifndef mingw32_HOST_OS
+import           System.Console.ANSI
+#endif
+
+main :: IO ()
+main = hspec spec
+
+testSpec :: H.Spec
+testSpec = do
+  H.describe "Example" $ do
+    H.it "success"    (H.Success)
+    H.it "fail 1"     (H.Fail "fail message")
+    H.it "pending"    (H.pending "pending message")
+    H.it "fail 2"     (H.Fail "")
+    H.it "exceptions" (undefined :: H.Result)
+    H.it "fail 3"     (H.Fail "")
+
+spec :: Spec
+spec = do
+  describe "silent" $ do
+    let runSpec = fmap fst . capture . H.hspecWith H.defaultConfig {H.configFormatter = H.silent}
+    it "produces no output" $ do
+      runSpec testSpec `shouldReturn` ""
+
+  describe "failed_examples" $ do
+    failed_examplesSpec H.failed_examples
+
+  describe "progress" $ do
+    let runSpec = capture__ . H.hspecWith H.defaultConfig {H.configFormatter = H.progress}
+
+    it "produces '..F...FF.F' style output" $ do
+      r <- runSpec testSpec
+      head r `shouldBe` ".F.FFF"
+
+    context "same as failed_examples" $ do
+      failed_examplesSpec H.progress
+
+  describe "specdoc" $ do
+    let runSpec = capture__ . H.hspecWith H.defaultConfig {H.configFormatter = H.specdoc}
+
+    it "displays a header for each thing being described" $ do
+      _:x:_ <- runSpec testSpec
+      x `shouldBe` "Example"
+
+    it "displays one row for each behavior" $ do
+      r <- runSpec $ do
+        H.describe "List as a Monoid" $ do
+          H.describe "mappend" $ do
+            H.it "is associative" True
+          H.describe "mempty" $ do
+            H.it "is a left identity" True
+            H.it "is a right identity" True
+        H.describe "Maybe as a Monoid" $ do
+          H.describe "mappend" $ do
+            H.it "is associative" True
+          H.describe "mempty" $ do
+            H.it "is a left identity" True
+            H.it "is a right identity" True
+      normalizeSummary r `shouldBe` [
+          ""
+        , "List as a Monoid"
+        , "  mappend"
+        , "    - is associative"
+        , ""
+        , "  mempty"
+        , "    - is a left identity"
+        , "    - is a right identity"
+        , ""
+        , "Maybe as a Monoid"
+        , "  mappend"
+        , "    - is associative"
+        , ""
+        , "  mempty"
+        , "    - is a left identity"
+        , "    - is a right identity"
+        , ""
+        , "Finished in 0.0000 seconds"
+        , "6 examples, 0 failures"
+        ]
+
+    it "prints an empty line before each group" $ do
+      r <- runSpec $ do
+        H.describe "foo" $ do
+          H.it "example 1" True
+          H.it "example 2" True
+          H.describe "bar" $ do
+            H.it "example 3" True
+            H.it "example 4" True
+      normalizeSummary r `shouldBe` [
+          ""
+        , "foo"
+        , "  - example 1"
+        , "  - example 2"
+        , ""
+        , "  bar"
+        , "    - example 3"
+        , "    - example 4"
+        , ""
+        , "Finished in 0.0000 seconds"
+        , "4 examples, 0 failures"
+        ]
+
+    it "prints an empty line after each group" $ do
+      r <- runSpec $ do
+        H.describe "foo" $ do
+          H.describe "bar" $ do
+            H.it "example 1" True
+            H.it "example 2" True
+          H.it "example 3" True
+          H.it "example 4" True
+      normalizeSummary r `shouldBe` [
+          ""
+        , "foo"
+        , "  bar"
+        , "    - example 1"
+        , "    - example 2"
+        , ""
+        , "  - example 3"
+        , "  - example 4"
+        , ""
+        , "Finished in 0.0000 seconds"
+        , "4 examples, 0 failures"
+        ]
+
+    it "outputs an empty line at the beginning (even for non-nested specs)" $ do
+      r <- runSpec $ do
+        H.it "example 1" True
+        H.it "example 2" True
+      normalizeSummary r `shouldBe` [
+          ""
+        , "- example 1"
+        , "- example 2"
+        , ""
+        , "Finished in 0.0000 seconds"
+        , "2 examples, 0 failures"
+        ]
+
+    it "displays a row for each successfull, failed, or pending example" $ do
+      r <- runSpec testSpec
+      r `shouldSatisfy` any (== "  - fail 1 FAILED [1]")
+      r `shouldSatisfy` any (== "  - success")
+
+    it "displays a '#' with an additional message for pending examples" $ do
+      r <- runSpec testSpec
+      r `shouldSatisfy` any (== "     # PENDING: pending message")
+
+    it "outputs failed examples in red, pending in yellow, and successful in green" $ do
+      pending
+
+    context "same as failed_examples" $ do
+      failed_examplesSpec H.progress
+
+failed_examplesSpec :: H.Formatter -> Spec
+failed_examplesSpec formatter = do
+  let runSpec = capture__ . H.hspecWith H.defaultConfig {H.configFormatter = formatter}
+
+  it "summarizes the time it takes to finish" $ do
+    r <- runSpec (return ())
+    normalizeSummary r `shouldSatisfy` any (== "Finished in 0.0000 seconds")
+
+  context "displays a detailed list of failures" $ do
+    it "prints all requirements that are not met" $ do
+      r <- runSpec testSpec
+      r `shouldSatisfy` any (== "1) Example fail 1 FAILED")
+
+    it "prints the exception type for requirements that fail due to an uncaught exception" $ do
+      r <- runSpec $ do
+        H.it "foobar" (undefined :: Bool)
+      r `shouldContain` [
+          "1) foobar FAILED (uncaught exception)"
+        , "GHC.Exception.ErrorCall (Prelude.undefined)"
+        ]
+
+    it "prints all descriptions when a nested requirement fails" $ do
+      r <- runSpec $
+        H.describe "foo" $ do
+          H.describe "bar" $ do
+            H.it "baz" False
+      r `shouldSatisfy` any (== "1) foo.bar baz FAILED")
+
+  it "summarizes the number of examples and failures" $ do
+    r <- runSpec testSpec
+    r `shouldSatisfy` any (== "6 examples, 4 failures, 1 pending")
+
+  -- Windows has no support for ANSI escape codes.  The Console API is used for
+  -- colorized output, hence the following tests do not work on Windows.
+#ifndef mingw32_HOST_OS
+  it "shows summary in green if there are no failures" $ do
+    r <- capture__ $ H.hspecWith H.defaultConfig {H.configColorMode = H.ColorAlway} $ do
+      H.it "foobar" True
+    r `shouldSatisfy` any (== (green ++ "1 example, 0 failures" ++ reset))
+
+  it "shows summary in yellow if there are pending examples" $ do
+    r <- capture__ $ H.hspecWith H.defaultConfig {H.configColorMode = H.ColorAlway} $ do
+      H.it "foobar" H.pending
+    r `shouldSatisfy` any (== (yellow ++ "1 example, 0 failures, 1 pending" ++ reset))
+
+  it "shows summary in red if there are failures" $ do
+    r <- capture__ $ H.hspecWith H.defaultConfig {H.configColorMode = H.ColorAlway} $ do
+      H.it "foobar" False
+    r `shouldSatisfy` any (== (red ++ "1 example, 1 failure" ++ reset))
+
+  it "shows summary in red if there are both failures and pending examples" $ do
+    r <- capture__ $ H.hspecWith H.defaultConfig {H.configColorMode = H.ColorAlway} $ do
+      H.it "foo" False
+      H.it "bar" H.pending
+    r `shouldSatisfy` any (== (red ++ "2 examples, 1 failure, 1 pending" ++ reset))
+  where
+    green  = setSGRCode [SetColor Foreground Dull Green]
+    yellow = setSGRCode [SetColor Foreground Dull Yellow]
+    red    = setSGRCode [SetColor Foreground Dull Red]
+    reset  = setSGRCode [Reset]
+#endif
diff --git a/test/Test/Hspec/HUnitSpec.hs b/test/Test/Hspec/HUnitSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Hspec/HUnitSpec.hs
@@ -0,0 +1,83 @@
+module Test.Hspec.HUnitSpec (main, spec) where
+
+import           Test.Hspec.Meta
+import           Util (capture__)
+import           Control.Applicative
+
+import qualified Test.Hspec as H
+import qualified Test.Hspec.Runner as H
+import           Test.Hspec.Core.Type (SpecTree(..), runSpecM)
+import           Test.Hspec.HUnit
+import           Test.HUnit
+
+main :: IO ()
+main = hspec spec
+
+-- SpecTree does not have an Eq nor a Show instance, hence we map it to `Tree`.
+data Tree = Group String [Tree] | Example String
+  deriving (Eq, Show)
+
+shouldYield :: Test -> [Tree] -> Expectation
+a `shouldYield` b = (convert . runSpecM . fromHUnitTest) a `shouldBe` b
+  where
+    convert :: [SpecTree] -> [Tree]
+    convert = map go
+      where
+        go :: SpecTree -> Tree
+        go x = case x of
+          SpecGroup s xs  -> Group s (map go xs)
+          SpecItem  s _   -> Example s
+
+spec :: Spec
+spec = do
+  describe "fromHUnitTest" $ do
+    let e = TestCase $ pure ()
+
+    it "works for a TestCase" $ do
+      e `shouldYield` [Example "<unlabeled>"]
+
+    it "works for a labeled TestCase" $ do
+      TestLabel "foo" e
+        `shouldYield` [Example "foo"]
+
+    it "works for a TestCase with nested labels" $ do
+      (TestLabel "foo" . TestLabel "bar") e
+        `shouldYield` [Group "foo" [Example "bar"]]
+
+    it "works for a flat TestList" $ do
+      TestList [e, e, e]
+        `shouldYield` [Example "<unlabeled>", Example "<unlabeled>", Example "<unlabeled>"]
+
+    it "works for a nested TestList" $ do
+      (TestLabel "foo" . TestLabel "bar" . TestList) [TestLabel "one" e, TestLabel "two" e, TestLabel "three" e]
+        `shouldYield` [Group "foo" [Group "bar" [Example "one", Example "two", Example "three"]]]
+
+  describe "HUnit TestCase as an example (deprecated!)" $ do
+    it "is specified with the HUnit `TestCase` data constructor" $ TestCase $ do
+      runSpec $ do
+        H.it "some behavior" (TestCase $ "foo" @?= "bar")
+        H.it "some behavior" (TestCase $ "foo" @?= "foo")
+      `shouldReturn` H.Summary 2 1
+
+    it "is the assumed example for IO() actions" $ do
+      runSpec $ do
+        H.it "some behavior" ("foo" @?= "bar")
+        H.it "some behavior" ("foo" @?= "foo")
+      `shouldReturn` H.Summary 2 1
+
+    it "will show the failed assertion text if available (e.g. assertBool)" $ do
+      let assertionText = "some assertion text"
+      r <- capture__ . runSpec $ do
+        H.describe "foo" $ do
+          H.it "bar" (assertFailure assertionText)
+      r `shouldSatisfy` any (== assertionText)
+
+    it "will show the failed assertion expected and actual values if available (e.g. assertEqual)" $ do
+      r <- capture__ . runSpec $ do
+        H.describe "foo" $ do
+          H.it "bar" (assertEqual "trivial" (1::Int) 2)
+      assertBool "should find assertion text" $ any (=="trivial") r
+      assertBool "should find 'expected: 1'"  $ any (=="expected: 1") r
+      assertBool "should find ' but got: 2'"  $ any (==" but got: 2") r
+  where
+    runSpec = H.hspecWith H.defaultConfig
diff --git a/test/Test/Hspec/Meta.hs b/test/Test/Hspec/Meta.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Hspec/Meta.hs
@@ -0,0 +1,2 @@
+module Test.Hspec.Meta (module Test.Hspec) where
+import Test.Hspec
diff --git a/test/Test/Hspec/QuickCheckSpec.hs b/test/Test/Hspec/QuickCheckSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Hspec/QuickCheckSpec.hs
@@ -0,0 +1,19 @@
+module Test.Hspec.QuickCheckSpec (main, spec) where
+
+import           Test.Hspec.Meta
+
+import qualified Test.Hspec as H
+import qualified Test.Hspec.Runner as H
+import qualified Test.Hspec.QuickCheck as H
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec = do
+  describe "prop" $ do
+    it "is a shortcut to use properties as examples" $ do
+      H.hspecWith H.defaultConfig $ do
+        H.describe "read" $ do
+          H.prop "is inverse to show" $ \x -> (read . show) x == (x :: Int)
+      `shouldReturn` H.Summary 1 0
diff --git a/test/Test/Hspec/RunnerSpec.hs b/test/Test/Hspec/RunnerSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Hspec/RunnerSpec.hs
@@ -0,0 +1,214 @@
+module Test.Hspec.RunnerSpec (main, spec) where
+
+import           Test.Hspec.Meta
+import           System.IO.Silently (hCapture, hSilence)
+import           System.IO (stderr)
+import           Control.Applicative
+import           System.Environment (withArgs, withProgName, getArgs)
+import           System.Exit
+import qualified Control.Exception as E
+import           Util
+import           Mock
+import           System.SetEnv
+import           Test.Hspec.Util (getEnv)
+import           Test.QuickCheck.Property (morallyDubiousIOProperty)
+
+import qualified Test.Hspec.Runner as H
+import qualified Test.Hspec as H (describe, it, pending)
+import qualified Test.Hspec.Formatters as H (silent)
+
+ignoreExitCode :: IO () -> IO ()
+ignoreExitCode action = action `E.catch` \e -> let _ = e :: ExitCode in return ()
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec = do
+
+  describe "hspec" $ do
+    it "runs a spec" $ do
+      H.hspec $ do
+        H.it "foobar" True
+      `shouldReturn` ()
+
+    it "exits with exitFailure if not all examples pass" $ do
+      H.hspec $ do
+        H.it "foobar" False
+      `shouldThrow` (== ExitFailure 1)
+
+    it "suppresses output to stdout when evaluating examples" $ do
+      r <- capture__ . H.hspec $ do
+        H.it "foobar" $ do
+          putStrLn "baz"
+      r `shouldSatisfy` notElem "baz"
+
+    it "prints an error message on unrecognized command-line options" $ do
+      withProgName "myspec" . withArgs ["--foo"] $ do
+        hSilence [stderr] (H.hspec $ pure ()) `shouldThrow` (== ExitFailure 1)
+        fst `fmap` hCapture [stderr] (ignoreExitCode (H.hspec $ pure ())) `shouldReturn` unlines [
+            "myspec: unrecognized option `--foo'"
+          , "Try `myspec --help' for more information."
+          ]
+
+    it "does not leak command-line flags to examples" $ do
+      withArgs ["--verbose"] $ do
+        H.hspec $ do
+          H.it "foobar" $ do
+            getArgs `shouldReturn` []
+        `shouldReturn` ()
+
+    context "with --help" $ do
+      let printHelp = withProgName "spec" . withArgs ["--help"] . H.hspec $ pure ()
+      it "prints help" $ do
+        r <- (capture__ . ignoreExitCode) printHelp
+        r `shouldStartWith` ["Usage: spec [OPTION]..."]
+        printHelp `shouldThrow` (== ExitSuccess)
+
+      it "constrains lines to 80 characters" $ do
+        r <- (capture__ . ignoreExitCode) printHelp
+        r `shouldSatisfy` all ((<= 80) . length)
+        r `shouldSatisfy` any ((78 <=) . length)
+
+    context "with --verbose" $ do
+      it "does not suppress output to stdout" $ do
+        r <- capture__ . withArgs ["--verbose"] . H.hspec $ do
+          H.it "foobar" $ do
+            putStrLn "baz"
+        r `shouldSatisfy` elem "baz"
+
+    context "with --match" $ do
+      it "only runs examples that match a given pattern" $ do
+        e1 <- newMock
+        e2 <- newMock
+        e3 <- newMock
+        withArgs ["-m", "/bar/example"] . H.hspec $ do
+          H.describe "foo" $ do
+            H.describe "bar" $ do
+              H.it "example 1" $ mockAction e1
+              H.it "example 2" $ mockAction e2
+            H.describe "baz" $ do
+              H.it "example 3" $ mockAction e3
+        (,,) <$> mockCounter e1 <*> mockCounter e2 <*> mockCounter e3 `shouldReturn` (1, 1, 0)
+
+      it "can be given multiple times" $ do
+        e1 <- newMock
+        e2 <- newMock
+        e3 <- newMock
+        withArgs ["-m", "foo", "-m", "baz"] . H.hspec $ do
+          H.describe "foo" $ do
+            H.it "example 1" $ mockAction e1
+          H.describe "bar" $ do
+            H.it "example 2" $ mockAction e2
+          H.describe "baz" $ do
+            H.it "example 3" $ mockAction e3
+        (,,) <$> mockCounter e1 <*> mockCounter e2 <*> mockCounter e3 `shouldReturn` (1, 0, 1)
+
+    context "with --maximum-generated-tests=n" $ do
+      it "tries QuickCheck properties n times" $ do
+        m <- newMock
+        withArgs ["--maximum-generated-tests=23"] . H.hspec $ do
+          H.it "foo" $ do
+            morallyDubiousIOProperty (mockAction m >> pure True)
+        mockCounter m `shouldReturn` 23
+
+    context "with --print-cpu-time" $ do
+      it "includes used CPU time in summary" $ do
+        r <- capture_ $ withArgs ["--print-cpu-time"] (H.hspec $ pure ())
+        (normalizeSummary . lines) r `shouldContain` ["Finished in 0.0000 seconds, used 0.0000 seconds of CPU time"]
+
+    context "with --html" $ do
+      it "produces HTML output" $ do
+        r <- capture_ . withArgs ["--html"] . H.hspec $ do
+          H.it "foo" True
+        r `shouldContain` "</span>"
+
+      it "marks successful examples with CSS class hspec-success" $ do
+        r <- capture_ . withArgs ["--html"] . H.hspec $ do
+          H.it "foo" True
+        r `shouldContain` "<span class=\"hspec-success\">- foo\n</span>"
+
+      it "marks pending examples with CSS class hspec-pending" $ do
+        r <- capture_ . withArgs ["--html"] . H.hspec $ do
+          H.it "foo" H.pending
+        r `shouldContain` "<span class=\"hspec-pending\">- foo"
+
+      it "marks failed examples with CSS class hspec-failure" $ do
+        r <- capture_ . ignoreExitCode . withArgs ["--html"] . H.hspec $ do
+          H.it "foo" False
+        r `shouldContain` "<span class=\"hspec-failure\">- foo"
+
+  describe "hspec (experimental features)" $ do
+    it "stores a failure report in environment HSPEC_FAILURES" $ do
+      ignoreExitCode . H.hspec $ do
+        H.describe "foo" $ do
+          H.describe "bar" $ do
+            H.it "example 1" True
+            H.it "example 2" False
+        H.describe "baz" $ do
+          H.it "example 3" False
+      getEnv "HSPEC_FAILURES" `shouldReturn` Just "[([\"foo\",\"bar\"],\"example 2\"),([\"baz\"],\"example 3\")]"
+
+    describe "with --re-run" $ do
+      let runSpec = (capture__ . ignoreExitCode . H.hspec) $ do
+            H.it "example 1" True
+            H.it "example 2" False
+            H.it "example 3" False
+            H.it "example 4" True
+            H.it "example 5" False
+
+      it "re-runs examples that previously failed" $ do
+        r0 <- runSpec
+        r0 `shouldSatisfy` elem "5 examples, 3 failures"
+
+        r1 <- withArgs ["-r"] runSpec
+        r1 `shouldSatisfy` elem "3 examples, 3 failures"
+
+      context "when there is no failure report in the environment" $ do
+        it "runs everything" $ do
+          unsetEnv "HSPEC_FAILURES"
+          r <- hSilence [stderr] $ withArgs ["-r"] runSpec
+          r `shouldSatisfy` elem "5 examples, 3 failures"
+
+        it "prints a warning to stderr" $ do
+          unsetEnv "HSPEC_FAILURES"
+          r <- hCapture [stderr] $ withArgs ["-r"] runSpec
+          fst r `shouldBe` "WARNING: Could not read environment variable HSPEC_FAILURES; `--re-run' is ignored!\n"
+
+      context "when parsing of failure report fails" $ do
+        it "runs everything" $ do
+          setEnv "HSPEC_FAILURES" "some invalid report"
+          r <- hSilence [stderr] $ withArgs ["-r"] runSpec
+          r `shouldSatisfy` elem "5 examples, 3 failures"
+
+        it "prints a warning to stderr" $ do
+          setEnv "HSPEC_FAILURES" "some invalid report"
+          r <- hCapture [stderr] $ withArgs ["-r"] runSpec
+          fst r `shouldBe` "WARNING: Could not read environment variable HSPEC_FAILURES; `--re-run' is ignored!\n"
+
+  describe "hspecWith" $ do
+    it "returns a summary of the test run" $ do
+      H.hspecWith H.defaultConfig $ do
+        H.it "foo" True
+        H.it "foo" False
+        H.it "foo" False
+        H.it "foo" True
+        H.it "foo" True
+      `shouldReturn` H.Summary 5 2
+
+    it "treats uncaught exceptions as failure" $ do
+      H.hspecWith H.defaultConfig  $ do
+        H.it "foobar" (E.throwIO (E.ErrorCall "foobar") >> pure ())
+      `shouldReturn` H.Summary 1 1
+
+    it "uses the specdoc formatter by default" $ do
+      _:r:_ <- capture__ . H.hspecWith H.defaultConfig $ do
+        H.describe "Foo.Bar" $ do
+          H.it "some example" True
+      r `shouldBe` "Foo.Bar"
+
+    it "can use a custom formatter" $ do
+      r <- capture_ . H.hspecWith H.defaultConfig {H.configFormatter = H.silent} $ do
+        H.describe "Foo.Bar" $ do
+          H.it "some example" True
+      r `shouldBe` ""
diff --git a/test/Test/Hspec/UtilSpec.hs b/test/Test/Hspec/UtilSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Hspec/UtilSpec.hs
@@ -0,0 +1,89 @@
+module Test.Hspec.UtilSpec (main, spec) where
+
+import           Test.Hspec.Meta
+
+import qualified Control.Exception as E
+import           Test.Hspec.Util
+
+import           System.SetEnv
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec = do
+  describe "quantify" $ do
+    it "returns an amount and a word given an amount and word" $ do
+      quantify 1 "thing" `shouldBe` "1 thing"
+
+    it "returns a singular word given the number 1" $ do
+      quantify 1 "thing" `shouldBe` "1 thing"
+
+    it "returns a plural word given a number greater than 1" $ do
+      quantify 2 "thing" `shouldBe` "2 things"
+
+    it "returns a plural word given the number 0" $ do
+      quantify 0 "thing" `shouldBe` "0 things"
+
+  describe "safeEvaluate" $ do
+    it "returns Right on success" $ do
+      Right e <- safeEvaluate (return 23 :: IO Int)
+      e `shouldBe` 23
+
+    it "returns Left on exception" $ do
+      Left e <- safeEvaluate (E.throwIO E.DivideByZero :: IO Int)
+      show e `shouldBe` "divide by zero"
+
+    it "evaluates result to weak head normal form" $ do
+      Left e <- safeEvaluate (return undefined)
+      show e `shouldBe` "Prelude.undefined"
+
+    it "re-throws AsyncException" $ do
+      safeEvaluate (E.throwIO E.UserInterrupt :: IO Int) `shouldThrow` (== E.UserInterrupt)
+
+  describe "filterPredicate" $ do
+    it "tries to match a pattern against a path" $ do
+      let p = filterPredicate "foo/bar/example 1"
+      p (["foo", "bar"], "example 1") `shouldBe` True
+      p (["foo", "bar"], "example 2") `shouldBe` False
+
+    it "is ambiguous" $ do
+      let p = filterPredicate "foo/bar/baz"
+      p (["foo", "bar"], "baz") `shouldBe` True
+      p (["foo"], "bar/baz") `shouldBe` True
+
+    it "succeeds on a partial match" $ do
+      let p = filterPredicate "bar/baz"
+      p (["foo", "bar", "baz"], "example 1") `shouldBe` True
+
+    it "succeeds with a pattern that matches the message give in the failure list" $ do
+      let p = filterPredicate "ModuleA.ModuleB.foo does something"
+      p (["ModuleA", "ModuleB", "foo"], "does something") `shouldBe` True
+
+  describe "formatRequirement" $ do
+    it "creates a sentence from a subject and a requirement" $ do
+      formatRequirement (["reverse"], "reverses a list") `shouldBe` "reverse reverses a list"
+
+    it "creates a sentence from a subject and a requirement when the subject consits of multiple words" $ do
+      formatRequirement (["The reverse function"], "reverses a list") `shouldBe` "The reverse function reverses a list"
+
+    it "returns the requirement if no subject is given" $ do
+      formatRequirement ([], "reverses a list") `shouldBe` "reverses a list"
+
+    it "inserts context separated by commas" $ do
+      formatRequirement (["reverse", "when applied twice"], "reverses a list") `shouldBe` "reverse, when applied twice, reverses a list"
+
+    it "joins components of a subject with a dot" $ do
+      formatRequirement (["Data", "List", "reverse"], "reverses a list") `shouldBe` "Data.List.reverse reverses a list"
+
+    it "properly handles context after a subject that consists of several components" $ do
+      formatRequirement (["Data", "List", "reverse", "when applied twice"], "reverses a list") `shouldBe` "Data.List.reverse, when applied twice, reverses a list"
+
+  describe "getEnv" $ do
+    it "returns value of specified environment variable" $ do
+      setEnv "FOO" "bar"
+      getEnv "FOO" `shouldReturn` Just "bar"
+
+    it "returns Nothing if specified environment variable is not set" $ do
+      unsetEnv "FOO"
+      getEnv "FOO" `shouldReturn` Nothing
diff --git a/test/Test/HspecSpec.hs b/test/Test/HspecSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/HspecSpec.hs
@@ -0,0 +1,64 @@
+module Test.HspecSpec (main, spec) where
+
+import           Test.Hspec.Meta
+import           Util (capture__)
+import           Data.List (isPrefixOf)
+
+import qualified Test.Hspec.Core.Type as H (defaultParams)
+import           Test.Hspec.Core (SpecTree(..), Result(..), runSpecM)
+import qualified Test.Hspec as H
+import qualified Test.Hspec.Runner as H (hspecWith)
+import           Test.Hspec.Runner (defaultConfig)
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec = do
+  describe "pending" $ do
+    it "specifies a pending example" $ do
+      r <- runSpec $ do
+        H.it "foo" H.pending
+      r `shouldSatisfy` any (== "     # PENDING: No reason given")
+
+    it "accepts an optional message, which is included in the report" $ do
+      r <- runSpec $ do
+        H.it "foo" $ do
+          H.pending "for some reason"
+      r `shouldSatisfy` any (== "     # PENDING: for some reason")
+
+  describe "describe" $ do
+    let testSpec = do
+          H.describe "some subject" $ do
+            H.it "foo" True
+            H.it "bar" True
+            H.it "baz" True
+    it "takes a description of what the behavior is for" $ do
+      r <- runSpec testSpec
+      r `shouldSatisfy` any (== "some subject")
+
+    it "groups behaviors for what's being described" $ do
+      r <- filter (isPrefixOf "  - ") `fmap` runSpec testSpec
+      length r `shouldBe` 3
+
+    it "can be nested" $ do
+      let [SpecGroup foo [SpecGroup bar [SpecItem baz _]]] = runSpecM $ do
+            H.describe "foo" $ do
+              H.describe "bar" $ do
+                H.it "baz" True
+      (foo, bar, baz) `shouldBe` ("foo", "bar", "baz")
+
+  describe "it" $ do
+    it "takes a description of a desired behavior" $ do
+      let [SpecItem d _] = runSpecM (H.it "whatever" True)
+      d `shouldBe` "whatever"
+
+    it "takes an example of that behavior" $ do
+      let [SpecItem _ e] = runSpecM (H.it "whatever" True)
+      e H.defaultParams `shouldReturn` Success
+
+    it "can use a Bool, HUnit Test, QuickCheck property, or `pending` as an example"
+      pending
+  where
+    runSpec :: H.Spec -> IO [String]
+    runSpec = capture__ . H.hspecWith defaultConfig
diff --git a/test/Util.hs b/test/Util.hs
new file mode 100644
--- /dev/null
+++ b/test/Util.hs
@@ -0,0 +1,30 @@
+module Util where
+
+import           Data.List
+import           Data.Char
+import           Test.Hspec.Meta
+import qualified System.IO.Silently as S
+
+capture_ :: IO a -> IO String
+capture_ = fmap fst . S.capture
+
+capture__ :: IO a -> IO [String]
+capture__ = fmap lines . capture_
+
+shouldContain :: (Eq a, Show a) => [a] -> [a] -> Expectation
+x `shouldContain` y = x `shouldSatisfy` isInfixOf y
+
+shouldStartWith :: (Eq a, Show a) => [a] -> [a] -> Expectation
+x `shouldStartWith` y = x `shouldSatisfy` isPrefixOf y
+
+shouldEndWith :: (Eq a, Show a) => [a] -> [a] -> Expectation
+x `shouldEndWith` y = x `shouldSatisfy` isSuffixOf y
+
+-- replace times in summary with zeroes
+normalizeSummary :: [String] -> [String]
+normalizeSummary xs = map f xs
+  where
+    f x | "Finished in " `isPrefixOf` x = map g x
+        | otherwise = x
+    g x | isNumber x = '0'
+        | otherwise  = x
