July 23, 2015

Fixing negation in clojure.test

I find myself to often use clojure.test for writing application or library unit tests in Clojure. Sure, there are more quality alternatives like Midje, Speclj or test.check (although test.check is different kind of approach for testing), but I'm fond of clojure.test for a couple of simple reasons:

  • it is already bundled with Clojure
    • easy to start with
    • customizable enough
    • no funky syntax

However, clojure.test simple design is, well, weird sometimes.

Let say you have cool function (foo) that should throw Exception object when you pass 1 as argument. You decided this is by design and why not make sure things are working as expected:

(deftest foo-tests
  (testing "foo fails on 1"
    (is (thrown? Exception (foo 1)))))

Now, you want to make sure it does not throw exception for e.g. 2. If you try with this:

(testing "foo works for 2"
  (is (not (thrown? Exception (foo 2)))))

and you will be surprised to see how compiler spit out a long stacktrace directly in your eyes. When you dissect it, you will notice this:

Caused by: java.lang.RuntimeException: Unable to resolve symbol: thrown? in this context
        at clojure.lang.Util.runtimeException(Util.java:219)
        at clojure.lang.Compiler.resolveIn(Compiler.java:6874)

The reason is that thrown? is not a function nor macro, although it looks like, but a dispatch symbol. In short, it fails a duck tests miserably.

Thanks to macros, we can circumvent this design omission easily. Just remember, in this case thrown? is a symbol and this snippet:

(defmacro is-not-thrown? [e expr]
  `(is (not ('thrown? ~e ~expr))))

will do the magic. Tune it in our sample test and we will have fully functional and working test:

(deftest foo-tests
  (testing "foo fails on 1"
    (is (thrown? Exception (foo 1))))

  (testing "foo works for 2"
    (is-not-thrown? Exception (foo 2))))

The same trick you can apply for instance? and thrown-with-msg?.

Tags: clojure