7

I currently have the following test code:

testUpdate :: Test
testUpdate = testCase "update does change artist" $ do
  (created, Just revised, parents) <- mbTest $ do
    Just editor <- fmap entityRef <$> findEditorByName "acid2"

    created <- create editor startWith
    let artistId = coreMbid created

    newRev <- update editor (coreRevision created) expected

    editId <- openEdit
    includeRevision editId newRev
    apply editId

    found <- findLatest artistId
    parents <- revisionParents newRev

    return (created, found, parents)

  coreData revised @?= expected

  assertBool "The old revision is a direct parent of the new revision" $
    parents == [coreRevision created]

  where
    startWith = ...
    expected = ...

This kinda works, but it's messy. I'd much rather be able to write something without having to return the various things under test, and instead have the assertions where they make sense.

I see there is the Assertable class, but it seems like I'd probably have to end up reinventing a bunch of stuff.

ocharles
  • 6,172
  • 2
  • 35
  • 46
  • Great question, I remember wondering why everything needed IO in its type when I first used it. – Matt Fenwick Nov 02 '12 at 14:21
  • Does the monad support `liftIO`? – hammar Nov 02 '12 at 14:32
  • @hammar It does, and I'm not sure how I missed the fact that all I need to do is hoist those tests up with `liftIO`. However, I'll leave the question open, maybe there are other ways :) – ocharles Nov 02 '12 at 14:52

1 Answers1

1

Why not just have your monad return an IO computation of type IO a, which is a pure value? Since as in your comment, the case where the monad is an instance of MonadIO is trivial, assume that the monad allows pure computation:

newtype M a = M {runM :: ....}
instance Monad M where
  ...

makeTest :: M Assertion
makeTest = do
    created <- ..
    found   <- ..
    parents <- ..
    let test1 = coreData revised @?= expected
    ...
    let test2 = assertBool "The old revision..." $
                   parents == [coreRevision create]

    return $ test1 >> test2

testUpdate :: Test
testUpdate = testCase "update does change artist" $ runM makeTest

A bonus is that you could return a collection of tests by one monadic computation, just as you would in the list monad.

mnish
  • 1,869
  • 1
  • 13
  • 15
  • I don't really like this because all assertions run far later than the code that depends on the assertion. This will probably mean that the assertion won't actually fail, because the code itself might have already thrown an exception because the assertion didn't hold. – ocharles Nov 11 '12 at 11:20
  • Do you mean that the code between `test1` and `test2` in my example should catch an exception thrown by those tests? But, in that case, your monad must be an instance of MonadIO unless you are cheating (i.e. using `unsafe*`) becuase in Haskell pure code cannot catch an exception. As you said, this case is trivial to solve with 'liftIO'. Maybe you are forgetting Haskell is a _lazy_ language? The job of a lazy language is to ensure evaluation order doesn't matter (except the IO thing). I would really like to see an example where neither `liftIO` nor my solution works. – mnish Nov 12 '12 at 01:26