Resources in Tasty (update)
Published on
In a recent article I described how resources were introduced to the Tasty test framework, as well as some alternative approaches. This article describes the new API, introduced in Tasty 0.7.
To recap, there was a function, withResource
, that
handled creation/acquisition and disposal of resources, but if you
needed to access the resource directly in the tests, you had to store
the resource in an IORef
(or similar) as part of the
initialization routine.
At the time it seemed acceptable, but later I discovered that when the number of resources was bigger than one or two, or even not known in advance (when tests are generated rather than just written down), this was inconvenient enough to start looking for a different solution.
One of the major problems with tests receiving the resource value directly, as in
withResource :: IO a
-> (a -> IO ())
-> (a -> TestTree)
-> TestTree
… was that the resource could be used not only in the tests themselves, but to construct the tests, which is bad/wrong for a number of reasons. For instance, we don’t want to create the resources when we’re not running tests, but we still want to know which tests we have.
The solution I found is to pass not the value of the resource, but an IO action yielding the resource.
withResource :: IO a
-> (a -> IO ())
-> (IO a -> TestTree)
-> TestTree
Even though it’s an IO action, it doesn’t acquire the
resource, because such a resource wouldn’t be shared across multiple
tests, which is the semantics we’re after. Instead, it returns the
resource which has been acquired (think: reads from an
IORef
or MVar
). But thanks to it being an
IO
action, it can only be used inside a test, and not to
construct or alter tests based on the resource value.
Here’s a modified example from the last article which works with this new API:
import Test.Tasty
import Test.Tasty.HUnit
-- assumed defintions
data Foo
acquire :: IO Foo
release :: Foo -> IO ()
testWithFoo :: Foo -> Assertion
= undefined
(acquire, release, testWithFoo)
= do
main $
defaultMain
withResource acquire release tests
tests :: IO Foo -> TestTree
=
tests getResource "Tests"
testGroup "x" $ getResource >>= testWithFoo
[ testCase ]