0

Can I write a test case with Test.HUnit that checks whether a call throws an exception?

I only care whether it throws any error, regardless of what message it prints.

Chris Martin
  • 30,334
  • 10
  • 78
  • 137
bantmen
  • 748
  • 6
  • 17

2 Answers2

2

This isn't specific to HUnit, but you can write a function to check whether an IO value throws:

λ> :set -XScopedTypeVariables
λ> import Control.Exception
λ> import Data.Functor
λ> import System.Environment

λ> throws io = catch (io $> False) $ \(e :: SomeException) -> pure True
throws :: IO a -> IO Bool

λ> sequence $ throws <$> [ getEnv "HOME", getEnv "whatever", error "a" ]
[False,True,True]
Chris Martin
  • 30,334
  • 10
  • 78
  • 137
  • Yes, assuming you're using `error` to produce values of type `IO`. I've updated the answer to include this. – Chris Martin Dec 13 '16 at 18:12
  • Thank you. I am probably missing something obvious but when I enter your code into repl I get an error for `throws io = ...`: `Illegal type signature: 'SomeException' Type signatures are only allowed in patterns with ScopedTypeVariables` – bantmen Dec 13 '16 at 18:19
  • Sorry, I forgot I have some GHC extensions enabled by default in my `~/.ghc/ghci.conf`. Use `:set -XScopedTypeVariables` in the repl to enable that one. – Chris Martin Dec 13 '16 at 18:21
2

If by an "exception" you mean Exception and it is being thrown in some IO code, then you can use catch or catches. However, if you mean catching things like error "Something bad happened" in pure code, you are out of luck. If you are willing to do the handling in IO, you have more options:

ghci> import Control.Exception
ghci> catch (error "Eek") (\(ErrorCallWithLocation msg _) -> putStrLn msg) 
Eek

From the Haskell 2010 report section 3:

Errors during expression evaluation, denoted by ⊥ (“bottom”), are indistinguishable by a Haskell program from non-termination.

Here is another way to think about it: notice that the moment we try to evaluate a value that is ⊥ (like error "Help!") depends not on when this value was created but only when it was first needed (since Haskell is non-strict). A mechanism to catch this sort of error would then break referential transparency.

Alec
  • 31,829
  • 7
  • 67
  • 114