1

I'm very much still a beginner at Haskell. I learn best by trying things out, but sometimes I feel like I'm missing something fundamental. This is one of those times.

I have some code that calls Control.Exception.assert but the Assertion exception wasn't being thrown. I tracked this down to asserts being ignored by default (which is unusual since the docs suggest the opposite), so I added this to my package.yaml:

library:
  ghc-options:
    - -fno-ignore-asserts

I'm not using -O1 or -O2 or -fignore-asserts so I don't know why the default behaviour is to ignore assert, but that's not what this question is really about.

I want to write an Hspec unit test to check this behaviour (and also as a chance to learn a bit about Hspec).

In my Lib module:

-- Lib.hs
module Lib (checkAssert) where

import Control.Exception

checkAssert :: Bool
checkAssert = assert (1 == 2) False

This module is compiled with -fno-ignore-asserts.

And in my Spec.hs (I'm actually using Tasty to host the tests):

import Test.Tasty
import Test.Tasty.Hspec
import Lib
import GHC.IO.Unsafe (unsafePerformIO)
import qualified Control.Exception (assert, AssertionFailed)
import Control.Monad

assertionException :: Selector Control.Exception.AssertionFailed
assertionException = const True

-- Hspec tests
spec_assertRaises :: Spec
spec_assertRaises = do
  it "assert" $ (liftM checkAssert) `shouldThrow` assertionException

unitTests :: TestTree
unitTests = testGroup "Lib Unit Tests"
  [ -- add Hspec tests to Tasty TestTree
    unsafePerformIO (testSpec "spec" spec_assertRaises)
  ]

main :: IO()
main = defaultMain unitTests

This test module is not compiled with -fno-ignore-asserts. I think this is OK because it doesn't actually call assert itself, it just calls the function checkAssert in the Lib module, which is compiled with that flag.

The problem I'm having is actually related to how non-monadic functions are called from Monads like Spec. From my reading, I can use fmap or liftM to call a non-monadic function like checkAssert - but I'm not able to get this to compile:

Couldn't match expected type ‘a10 -> r0’ with actual type ‘Bool’
• In the first argument of ‘liftM’, namely ‘checkAssert’

Can someone explain where I'm going wrong with this, please?

Note: I became aware that Test.HUnit.Tools has assertRaises and I initially tried to use this instead, but Test.Tasty.HUnit does not include this function for some reason.

davidA
  • 12,528
  • 9
  • 64
  • 96
  • The type of `liftM` is `Monad m => (a -> r) -> m a -> m r`. It takes a function and a monadic action, and applies the function to the action's result. `checkAssert` is not a function. – danidiaz May 31 '20 at 14:21
  • When you say that `checkAssert` is not a function, what do you mean exactly? Does a function always need to take at least one parameter? What is it if it's not a function? If I changed `checkAssert` to `checkAssert :: Bool -> Bool` `checkAssert x = assert (1 == 2) x` would that be sufficient? – davidA Jun 01 '20 at 05:11
  • 1
    In your code, `checkAssert` has type bool `Bool`. Only values with at type like `something -> otherthing` can accept parameters (which must be of type `something`). I'm usure about possible solutions because I don't know the type of `shouldThrow`. Aslo, an unrelated tip: instead of using that `unsafePerformIO`, it would look better if you did something like `x <- testSpec "spec" spec_assertRaises` in a do-block inside your `main`, and then constructed the `TestTree` in a `let` in the do-block, before invoking `defaultMain unitTests`. – danidiaz Jun 01 '20 at 11:58

0 Answers0