A couple of weeks ago I wrote about typed assertions for Lazytest.
Like so many things, it seemed like a good idea at the time. Define typed objects for each kind of assertion (e.g., equality, instanceof). When a test fails, throw an exception with one such object as the payload. The type of the object describes the failure (e.g., not equal, not instance of) and its fields describe the components of the failure (e.g., X was not equal to Y).
It worked, but as Rich Hickey himself pointed out, it was redundant. All these typed objects have no methods, just different kinds of fields. Furthermore, most of the fields aren’t even that different. Every “failure” can be identified by a predicate and the arguments which caused it to return false.
I should have known something was wrong as soon as I wrote a Protocol with no methods. I was doing JavaBean-style programming, using objects to represent structured data. I can represent them more simply as maps.
What’s left is a much shorter form of the
expect macro that examines the assertion expression and, if it contains a normal function call, evaluates its arguments before calling the function. Effectively, it follows the same evaluation rules as Clojure itself, while holding on to the intermediate values. A failing test throws an exception with an attached map holding the original unevaluated form, the evaluated components, and the return value of the predicate.