Rematch: composable assertions with good error messages

I kept on running into this thing where I was calling error in Haskell's QuickCheck just get good error messages about a thing I was comparing on equality. In Java land, getting good error messages from tests is handled by Hamcrest: a library for composable assertions with good error messages. This library is basically a port of hamcrest's core api, but I've been very pleased with how it turned out.

I've been using this in tests for production code for a month or so now, and I'm very pleased with it.

Running a matcher (in this example in an hunit test) looks like this:

expect [1] (is [1])

The core API is very simple:

data Matcher a = Matcher {
    match :: a -> Bool
  -- ^ A function that returns True if the matcher should pass, False if it
should fail
  , description :: String
  -- ^ A description of the matcher (usually of its success conditions)
  , describeMismatch :: a -> String
  -- ^ A description to be shown if the match fails.
  }

This means you can add/write your own matchers happily, which occasionally means you can write very nice test code (here's an example of using a custom matcher for checking the state of an "issue" in a hypothetical issue tracking app):

expect latestIssue (hasState Resolved)

-- I removed the supporting code to make this assertion actually run,
-- this post is already pretty long.

There are numerous matchers (and functions for creating matchers) in the rematch library, including some composition functions that provide good failure messages.

There are some shims to hook rematch into the common haskell test frameworks (specifically hunit and quickcheck).

The two libraries are up on hackage (there are some additional matchers for Text that aren't in core): http://hackage.haskell.org/package/rematch http://hackage.haskell.org/package/rematch-text

The code is all up on github:

http://github.com/tcrayford/rematch

I get rather frustrated when my tests give bad failure explanations, and using rematch goes a long way to fix that.

Lastly, rematch is pretty isolated from test frameworks/etc, with a very small and easy to understand surface api. Hopefully it'll help with the thing I've seen in other languages (cough ruby cough) with every test framework reinventing this idea, and not all frameworks having all the matchers I want to use.

I'd love to hear feedback/thoughts from folks.