Is it possible to use HUnit with test-framework in a monad other than IO?
Asked Answered
C

1

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.

Caseate answered 2/11, 2012 at 14:17 Comment(3)
Great question, I remember wondering why everything needed IO in its type when I first used it.Mossberg
Does the monad support liftIO?Feliks
@Feliks 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 :)Caseate
M
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.

Mclaurin answered 10/11, 2012 at 8:11 Comment(2)
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.Caseate
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.Mclaurin

© 2022 - 2024 — McMap. All rights reserved.