diff --git a/hspec2.cabal b/hspec2.cabal
--- a/hspec2.cabal
+++ b/hspec2.cabal
@@ -1,5 +1,5 @@
 name:             hspec2
-version:          0.3.4
+version:          0.4.0
 license:          MIT
 license-file:     LICENSE
 copyright:        (c) 2011-2014 Simon Hengel,
@@ -48,7 +48,7 @@
     , HUnit         >= 1.2.5
     , QuickCheck    >= 2.5.1
     , quickcheck-io
-    , hspec-expectations == 0.5.0.*
+    , hspec-expectations >= 0.5.0 && < 0.6.1
     , async >= 2
   exposed-modules:
       Test.Hspec
diff --git a/src/Test/Hspec.hs b/src/Test/Hspec.hs
--- a/src/Test/Hspec.hs
+++ b/src/Test/Hspec.hs
@@ -34,6 +34,7 @@
 , around_
 , aroundWith
 , parallel
+, runIO
 
 -- * Running a spec
 , hspec
@@ -50,7 +51,7 @@
 
 -- | Combine a list of specs into a larger spec.
 describe :: String -> SpecWith a -> SpecWith a
-describe label action = fromSpecList [Core.describe label (runSpecM action)]
+describe label action = fromSpecList [Core.describe label [BuildSpecs $ runSpecM action]]
 
 -- | An alias for `describe`.
 context :: String -> SpecWith a -> SpecWith a
@@ -104,7 +105,7 @@
     go spec = do
       mvar <- newMVar Nothing
       let action_ = memoize mvar action
-      return . runSpecM $ before action_ spec
+      runSpecM $ before action_ spec
 
 memoize :: MVar (Maybe a) -> IO a -> IO a
 memoize mvar action = modifyMVar mvar $ \ma -> case ma of
@@ -120,7 +121,7 @@
     go spec = do
       mvar <- newMVar Nothing
       let action_ = memoize mvar . action
-      return . runSpecM $ aroundWith (\e x -> action_ x >>= e) spec
+      runSpecM $ aroundWith (\e x -> action_ x >>= e) spec
 
 -- | Run a custom action after every spec item.
 after :: ActionWith a -> SpecWith a -> SpecWith a
diff --git a/src/Test/Hspec/Core/Type.hs b/src/Test/Hspec/Core/Type.hs
--- a/src/Test/Hspec/Core/Type.hs
+++ b/src/Test/Hspec/Core/Type.hs
@@ -19,13 +19,16 @@
 , it
 , forceResult
 
+, runIO
+
 , pending
 , pendingWith
 ) where
 
 import qualified Control.Exception as E
 import           Control.Applicative
-import           Control.Monad.Trans.Writer (Writer, execWriter, tell)
+import           Control.Monad.Trans.Writer
+import           Control.Monad.IO.Class (liftIO)
 import           Data.Typeable (Typeable)
 import           Data.List (isPrefixOf)
 import           Data.Maybe (fromMaybe)
@@ -47,17 +50,26 @@
 type SpecWith a = SpecM a ()
 
 -- | A writer monad for `SpecTree` forests.
-newtype SpecM a r = SpecM (Writer [SpecTree a] r)
+newtype SpecM a r = SpecM (WriterT [SpecTree a] IO r)
   deriving (Functor, Applicative, Monad)
 
 -- | Convert a `Spec` to a forest of `SpecTree`s.
-runSpecM :: SpecWith a -> [SpecTree a]
-runSpecM (SpecM specs) = execWriter specs
+runSpecM :: SpecWith a -> IO [SpecTree a]
+runSpecM (SpecM specs) = execWriterT specs
 
 -- | Create a `Spec` from a forest of `SpecTree`s.
 fromSpecList :: [SpecTree a] -> SpecWith a
 fromSpecList = SpecM . tell
 
+-- | Run an IO action while constructing the spec tree.
+--
+-- `SpecM` is a monad to construct a spec tree, without executing any spec
+-- items.  `runIO` allows you to run IO actions during this construction phase.
+-- The IO action is always run when the spec tree is constructed (e.g. even
+-- when @--dry-run@ is specified).
+runIO :: IO r -> SpecM a r
+runIO = SpecM . liftIO
+
 -- | The result of running an example.
 data Result = Success | Pending (Maybe String) | Fail String
   deriving (Eq, Show, Read, Typeable)
@@ -93,7 +105,7 @@
 type ActionWith a = a -> IO ()
 
 mapSpecItem :: (Item a -> Item b) -> SpecWith a -> SpecWith b
-mapSpecItem f = fromSpecList . map go . runSpecM
+mapSpecItem f = fromSpecList . return . BuildSpecs . fmap (map go) . runSpecM
   where
     go spec = case spec of
       SpecItem r item -> SpecItem r (f item)
diff --git a/src/Test/Hspec/Formatters/Internal.hs b/src/Test/Hspec/Formatters/Internal.hs
--- a/src/Test/Hspec/Formatters/Internal.hs
+++ b/src/Test/Hspec/Formatters/Internal.hs
@@ -147,7 +147,7 @@
 
 -- | used to notify the progress of the currently evaluated example
 --
--- NOTE: This is only called when interactive/color mode.
+-- /Note/: This is only called when interactive/color mode.
 , exampleProgress     :: Handle -> Path -> Progress -> IO ()
 
 -- | evaluated after each successful example
diff --git a/src/Test/Hspec/Runner/Tree.hs b/src/Test/Hspec/Runner/Tree.hs
--- a/src/Test/Hspec/Runner/Tree.hs
+++ b/src/Test/Hspec/Runner/Tree.hs
@@ -10,7 +10,7 @@
   deriving (Eq, Show, Functor)
 
 toTree :: SpecWith a -> IO [Tree (Item a)]
-toTree spec = concat <$> mapM go (runSpecM spec)
+toTree spec = concat <$> (runSpecM spec >>= mapM go)
   where
     go x = case x of
       SpecGroup label xs -> return . Node label . concat <$> mapM go xs
diff --git a/test/Test/HspecSpec.hs b/test/Test/HspecSpec.hs
--- a/test/Test/HspecSpec.hs
+++ b/test/Test/HspecSpec.hs
@@ -4,10 +4,12 @@
 import           Data.IORef
 import           Data.List (isPrefixOf)
 
-import           Test.Hspec.Core (SpecTree(..), Item(..), Result(..), runSpecM)
+import           Test.Hspec.Core (Item(..), Result(..))
 import qualified Test.Hspec as H
 import qualified Test.Hspec.Runner as H (hspecResult)
 
+import           Test.Hspec.Runner.Tree
+
 main :: IO ()
 main = hspec spec
 
@@ -41,29 +43,29 @@
       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
+      [Node foo [Node bar [Leaf baz _]]] <- toTree $ do
+        H.describe "foo" $ do
+          H.describe "bar" $ do
+            H.it "baz" True
       (foo, bar, baz) `shouldBe` ("foo", "bar", "baz")
 
     context "when no description is given" $ do
       it "uses a default description" $ do
-        let [SpecGroup d _] = runSpecM (H.describe "" (pure ()))
+        [Node d _] <- toTree (H.describe "" (pure ()))
         d `shouldBe` "(no description given)"
 
   describe "it" $ do
     it "takes a description of a desired behavior" $ do
-      let [SpecItem requirement _] = runSpecM (H.it "whatever" True)
+      [Leaf requirement _] <- toTree (H.it "whatever" True)
       requirement `shouldBe` "whatever"
 
     it "takes an example of that behavior" $ do
-      let [SpecItem _ item] = runSpecM (H.it "whatever" True)
+      [Leaf _ item] <- toTree (H.it "whatever" True)
       itemExample item defaultParams ($ ()) noOpProgressCallback `shouldReturn` Success
 
     context "when no description is given" $ do
       it "uses a default description" $ do
-        let [SpecItem requirement _] = runSpecM (H.it "" True)
+        [Leaf requirement _] <- toTree (H.it "" True)
         requirement `shouldBe` "(unspecified behavior)"
 
   describe "example" $ do
@@ -75,14 +77,14 @@
 
   describe "parallel" $ do
     it "marks examples for parallel execution" $ do
-      let [SpecItem _ item] = runSpecM . H.parallel $ H.it "whatever" True
+      [Leaf _ item] <- toTree . H.parallel $ H.it "whatever" True
       itemIsParallelizable item `shouldBe` True
 
     it "is applied recursively" $ do
-      let [SpecGroup _ [SpecGroup _ [SpecItem _ item]]] = runSpecM . H.parallel $ do
-            H.describe "foo" $ do
-              H.describe "bar" $ do
-                H.it "baz" True
+      [Node _ [Node _ [Leaf _ item]]] <- toTree . H.parallel $ do
+        H.describe "foo" $ do
+          H.describe "bar" $ do
+            H.it "baz" True
       itemIsParallelizable item `shouldBe` True
 
   describe "before" $ do
