hspec/hspec — pull request #154 by wuzzeb (2014-01-04 03:30:55+00:00)
Title: [RFC] bracketing tests

Hey, I've been thinking about Issue #103 and came up with a much less intrusive proposal than what is suggested in Issue #103. The goal I have is to be able to test MonadIO actions by splitting them up into multiple `it` expectations but allow the tests to share state. This has a couple uses:

* allocating and then destroying some expensive resource but sharing across tests

* testing a sequence of actions in some monad, but splitting it into multiple `it` blocks so an error is easier to find. The example I think about here is http://hackage.haskell.org/package/yesod-test, where the monad tracks cookies and responses. Currently yesod-test has to define its own ydescribe and yit and YesodSpec types, but it could be changed to be built on top of this pull request.

To facilitate this, only a few changes are needed. First, we need to be able to pass arbitrary parameters to tests, so I changed the Params and Item to

~~~
data SomeParam = forall p. Typeable p => SomeParam p

data Item = Item {
itemIsParallelizable :: Bool
, itemRequirement :: String
, itemExample :: [SomeParam] -> (IO () -> IO ()) -> IO Result
}
~~~

Then some mechanical changes to make the examples use `cast` to find the parameters from the list. While I was at it, I split the existing params into three.

Next, I changed the SpecTree to

~~~
data SpecTree =
SpecGroup String [SpecTree]
| SpecItem Item
| SpecBracket (IO SomeParam) (SomeParam -> IO ()) SpecTree
~~~

The bracket causes the creation to be run once, the param to appear on all child items, and once all items complete the close function to be run. This allows resource allocation but also allows testing sequences of complex monads by sticking an IORef into the `SomeParam`. Combined with MonadBaseControl, you could even do this mostly automatically by using an IORef containing a StM.

Thoughts? If you like this approach, I can work on it some more. I still have no tests and have not yet exposed the bracket outside of `Core`. (Most likely, the users of bracket will be other packages like `yesod-test` that present some high-level API.)