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 via clojure-expectations/clojure-test.

Clojure CLI/deps.edn: com.github.seancorfield/expectations {:mvn/version "RELEASE"}

Leiningen/project.clj: [com.github.seancorfield/expectations "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.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.

You can intermix other code inside defexpect and you can nest expect forms inside other code.

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)))

Note that expectations.clojure.test does not run tests on shutdown. Instead, tests are run on-demand via the usual clojure.test machinery and test runners.

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.