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.

As of 2.2.0-rc3, expectations.clojure.test should be used standalone, without requiring expectations - all public symbols from the latter are now available in the former. In particular, be aware that both namespaces now define an expect macro and they behave differently!

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

;; 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.

All the regular clojure.test functionality should be in play at this point, including use-fixtures instead of Expectations' own in-context/before-run/after-run functionality.

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.

As of version 2.2.0-beta2, you can intermix other code inside defexpect and you can nest expect forms inside other code. As of 2.2.0-rc3, this is a fully supported feature!

As of version 2.2.0-rc1, you can wrap blocks of code in expecting to provide more descriptive test output -- mirroring clojure.test/testing:

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

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

;; a named group of expectations
(defexpect group
  (expecting "conj should add each element"
    (expect [1 2] (conj [] 1 2))
    (expect #{1 2} (conj #{} 1 2)))
  (expect {1 2} (assoc {} 1 2)))

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 if you decide 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, stable 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, Cursive, and the more recent ProtoREPL (for Atom) -- are going to focus on Clojure's built-in testing library first. Support for the original form of Expectations, using unnamed tests, is non-existent in Cursive, and can be problematic in other editors. 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 namespace at a time, 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 lose some of the clarity and expressiveness there. As of 2.2.0-rc3, I think most of the original expressiveness has been restored -- although there is a little bit of repetition since the output now contains both clojure.tests "expected" and "actual" as well as the more elaborate failure messages from Expectations which may also include those.