# quickcheck-property-monad

## Introduction

When your data has many invariants, it's often difficult to write `Arbitrary`

instances for QuickCheck. This library attempts to solve that
problem by providing a nice interface to write QuickCheck tests without using `Arbitrary`

instances. It aims to be somewhere in the middle between
`HUnit`

and `QuickCheck`

: Use the random test case generation of `QuickCheck`

, but write `HUnit`

like assertions.

## A simple model

To show the library in action, let's first create a simple model holding an integer with a one invariant: the value hold by it will always be even.
We also provide two operations that preserve the invariant, `add2`

and `multiply`

, a function to create a new model and a function to check that the
invariant holds (we will use this function later when we write our tests):

```
module Model
( Model() -- We don't export the constructor so the invariant cannot be broken.
, newModel
, add2
, multiply
, checkInvariant
) where
data Model = Model { value :: Int } deriving Show
newModel :: Model
newModel = Model 0
add2 :: Model -> Model
add2 (Model x) = Model $ 2 + x
multiply :: Int -> Model -> Model
multiply n (Model x) = Model $ n * x
checkInvariant :: Model -> Bool
checkInvariant (Model x) = even x
```

## Writing tests using the `PropM`

monad

We now want to write tests that ensure that none of our operations will ever break the invariant, no matter in what sequence we apply them. In this case, we could
write an `Arbitrary`

instance for our model, but for more complex models, this will quickly become very difficult. Often, you have to use the functions you want to test
to create the `Arbitrary`

instance, which means that if the functions are broken, you already generate invalid data to begin with. It's difficult to find the bug in that case.

```
module Main where
import Model
import Test.QuickCheck
import Test.QuickCheck.Property.Monad
```

But using `quickcheck-property-monad`

, we can write the tests so that they will fail right after the invariant is broken. First, we define a `Gen`

to generate a random
operation. We will also include a short description of the operation, to make debugging easier:

```
randomOperation :: Gen (String, Model -> Model)
randomOperation = oneof
[ return ("Add two", add2)
, fmap (\n -> ("Multiply by " ++ show n, multiply n)) arbitrary
]
```

So far, all functions we've used are provided by `QuickCheck`

itself. Let's now write the test property:

```
prop_satisfies_invariant :: Property
prop_satisfies_invariant = sized $ \s -> property $ go newModel s
where go :: Model -> Int -> PropM Bool
go _ 0 = return True
go model size = do
(description, operation) <- generate randomOperation
logMessageLn $ "Operation: " ++ description
let model' = operation model
logMessageLn $ "Model is now: " ++ show model'
assert "Number is even" $ checkInvariant model'
go model' $ pred size
```

Here we've used some functions from `quickcheck-property-monad`

. We first grab the `size`

parameter from QuickCheck using `sized`

, and then
pass that to go, together with an initial model. `go`

returns a value of type `PropM Bool`

, which we have to convert into a QuickCheck `Property`

using the `property`

function.

But what does go do? First, it looks at the size parameter. If the size is null, we return `True`

, indicating a successful test. If the size is not
null, we first generate a random operation, using our previously defined `randomOperation`

function. We use `generate`

to lift the `Gen`

into the `PropM`

monad. After we generated a random operation, we log it. You'll see all messages logged with `logMessageLn`

when the test fails, which is useful for
debugging. We then apply the operation on the model. Using `assert`

, we require that the model still satisfies our invariant. `assert`

will do nothing
if the condition given to it is True. If it is False, it will abort the test case and report a failure, with the given error message. After that, we recurse,
decreasing the size by one so that we eventually reach 0 and stop.

Now, the only function left to write is main:

```
main :: IO ()
main = quickCheck prop_satisfies_invariant
```

If we run our test suite, we get the expected output:

```
+++ OK, passed 100 tests.
```

All fine!

## How a failure looks

Now, if you want to see how a failing test looks like, go back and change

```
add2 (Model x) = Model $ 2 + x
```

to

```
add2 (Model x) = Model $ 1 + x
```

If we now run our tests, we get a failure, as expected:

```
*** Failed! Falsifiable (after 2 tests):
Operation: Add two
Model is now: Model {value = 1}
Assertion failed: Number is even
```

We get the output from the `logMessageLn`

calls and the message of the assertion that failed.

## Contributing

Contributions are always welcome. If you have any ideas, improvements or bug reports,
send a pull request or open a issue on github.