Announcing Matcha: Composable Assertions with Human-Readable Error Messages

This is a blog about the development of Yeller, The Exception Tracker with Answers

Read more about Yeller here

“I can’t understand the error messages from clojure.test”

“test.check doesn’t give good output messages”

Readable failure messages can make or break a test suite. Humans shouldn’t be scrolling through screens of junk output, just to find the one key that is different, or getting error messages that read like this:

expected: true
  actual: false

(for tests that aren’t just boolean checks).

Nor should you have to go parse lengthy expressions just to figure out the actual failure.

clojure.test ships with rudimentary to bad failure messages out of the box. Whilst there are a few libraries that monkeypatch clojure.test for better error messages, they still don’t let you express your intent fully.

In Java-land, folk have been using hamcrest for an awful long time now. Hamcrest gives you simple, easy to compose test assertions (actually any kind of boolean matching), with excellent failure messages.

So I Ported it to Clojure

Here’s what it looks like:

(require '[matcha :as m])

(m/is (m/= 1)
      2)

FAIL in clojure.lang.PersistentList$EmptyList@1 (form-init7442466412675367144.clj:1)
expected: 2
  actual: 1 <class java.lang.Long>

That’s not much of an improvement over clojure.test, because the values are so simple. But for more advanced tests, matcha can dramatically improve on clojure.test

(m/is (m/has-entry :a 2) {})

FAIL in clojure.lang.PersistentList$EmptyList@1 (form-init7442466412675367144.clj:1)
expected: a map with entry :a 2
  actual: a map not containing the key :a (was {})

Or for a more complex example: Yeller has quite a few tests that assert the count of a particular collection is a certain number. When these kinds of tests fail, I used to (before matcha) find myself going and editing the test to show me the collection whose count was wrong.

(m/is (m/has-count 1) {})

FAIL in clojure.lang.PersistentList$EmptyList@1 (form-init7442466412675367144.clj:1)
Expected: a collection with count 1
 but was: {} with count 0

Matchers are simple, just a map with some functions in it:

{:match
 ;; a function that returns true if the value matches
 (constantly true)

 :description
 ;; a string describing the expected value
 "true"

 :describe-mismatch
 ;; a function that takes the actual value, and returns a string describing the mismatch if there is one
 pr-str}

There are a lot of matchers in matcha, and it’s easy to define your own. There are quite a few combinators that work on matchers too - the simplest is not:

(m/is (m/not (m/= 1))
      1)

FAIL in clojure.lang.PersistentList$EmptyList@1 (form-init7442466412675367144.clj:1)
expected: not 1
  actual: 1

Most of the matchers in matcha line up with clojure.core, so there’s not all that much learning to get started. There are a lot of matchers - for things like strings, clojure’s collection interfaces, numbers, equality tests, regexes and many more.

Check out the api docs, and the code on github

References:

Thanks

Thanks to Reid Draper for collaborating with me on matcha.

This is a blog about the development of Yeller, the Exception Tracker with Answers.

Read more about Yeller here

Looking for more about running production applications, debugging, Clojure development and distributed systems? Subscribe to our newsletter: