Expectations

a minimalist's unit testing framework

This project is maintained by seancorfield and jaycfields

expectations

adding signal, removing noise

clojure.test compatibility

clojure.test compatibility is available as of the 2.2.0-alpha1 release. There is a new expectations.clojure.test namespace containing a defexpect macro that allows for named expectations to be defined, like clojure.test's deftest macro.

(ns example.test
  (:require [expectations :refer :all]
            [expectations.clojure.test :refer [defexpect]]))

;; a single named expectation
(defexpect two 2 (+ 1 1))

;; a named group of expectations
(defexpect group
  (expect [1 2] (conj [] 1 2))
  (expect #{1 2} (conj #{} 1 2))
  (expect {1 2} (assoc {} 1 2)))

Expectations defined this way can be run with regular clojure.test tooling, such as lein test or boot test, or via the "standard" test running commands within various Clojure-compatible editors that are designed to work with clojure.test.

The above code creates functions two and group that have :test metadata (containing the actual test expressions), and the functions themselves run clojure.test/test-var on themselves so you can call (two) and (group) to run those tests (and they will print out any failures), the same way deftest works.

When you require the expectations.clojure.test namespace, it automatically disables the default "run tests on shutdown" mode of expectations. Since most of the expectations tooling already disables that and runs tests explicitly, this should not cause too many surprises but it is worth being aware of this when you start to mix'n'match test styles.

migrating to clojure.test

Given the streamlined simplicity of expectations, you might wonder why you would want to migrate your expectations test suite to clojure.test-style named tests? The short answer is tooling! Whilst we have well-maintained plugins for Leiningen and Boot, as well as an Emacs mode, the reality is that Clojure tooling is constantly evolving and most of those tools -- such as the excellent CIDER and the more recent ProtoREPL (for Atom) -- are going to focus on Clojure's built-in testing library first. A whole ecosystem of tooling has grown up around clojure.test and to take advantage of that with expectations, we either need to develop compatible extensions to each and every tool or we need expectations to be compatible with clojure.test.

One of the big obstacles for that compatibility is that, by default, expectations generates "random" function names for test code (the function names are based on the hashcode of the text form of the expect body), which means the test name changes whenever the text of the test changes. To address that, the new expectations.clojure.test namespace introduces named expectations via the defexpect macro (mimicking clojure.test's deftest macro). Whilst this goes against the Test Names philosophy that expectations was created with, it buys us a lot in terms of tooling support!

You can convert your test suite piecemeal, either replacing expect with defexpect (and a name) or wrapping several expect forms with a single defexpect (and a name). You can do this one test at a time, or in larger pieces, but you'll need to run both types of tests explicitly via tooling:

jfields$ lein do expectations, test

jfields$ boot expectations test

In addition to broader tooling support, you also get access to anything that is built on top of clojure.test, such as JUnit XML output, automatically.

What do you lose? Well, clojure.test-based failure reporting isn't as sophisticated as expectations-based failure reporting so you will get just "expected" and "actual" values, and a message with slightly more detail for some types of failure. Unlike clojure.test itself, the "expected" and "actual" values show both the final computed value and the expression that created it. I hope to further improve this area in future, to restore some of the improvements from expectations' failure reporting.