Search code examples
haskelltestingmonadsquickcheck

Custom Testable instance with QuickCheck


I have a custom result/error monad, nothing fancy:

import Control.Monad (liftM, ap)

data MyResult a = Error String | Success a

instance Functor MyResult where
    fmap = liftM

instance Applicative MyResult where
    pure  = return
    (<*>) = ap

instance Monad MyResult where
    (Error   s) >>= f = Error s
    (Success a) >>= f = f a
    return = Success

I want values in this monad to instantiate QuickCheck's Testable class, so I can write properties that hold (i.e. pass tests) when a value is Success _, and not hold (i.e. fail tests) when a value is Error _.

For example, I would like to be able to write a test like this:

myProp :: Property
myProp = forAll (arbitrary :: Int)
    (\x -> if x == 0 then Error "0 not allowed" else Success ())

and have the String in the Error be the test failure message if the test fails.

I assume I need to instantiate Testable for MyResult:

instance Testable t => Testable (MyResult t) where
    property (Success _) = undefined -- what goes here?
    property (Error   s) = undefined -- what goes here?

However I can't figure out how I should implement the property function in Testable, despite reading the QuickCheck documentation and relevant source code.

Any help here is greatly appreciated.


Solution

  • You need to make a Property from your result. The easiest way to do this is

    property (Success _) = property True
    property (Error _)   = property False
    

    You could also convert your Error result to a Quickcheck Result

    property (Error s)   = property $ failed { reason = s }
    

    See https://hackage.haskell.org/package/QuickCheck-2.9.2/docs/src/Test-QuickCheck-Property.html for the definition of failed