TDD in Clojure with Midje

One of the things I love about Clojure is that it has great support for writing unit tests. For starters you have clojure.test built-in to the Clojure API. The great thing about that is you don’t have any extra dependencies, once you have Clojure you can start writing tests. But if you don’t like clojure.test there are several other unit test frameworks you can use with differing approaches to unit testing. One these is Midje by Brian Marick. Midje is a unit test framework that encourages readable tests that can be written bottom-up or top-down and tests are written as facts that are asserted against the code under test. In the rest of this post I’m going to show how to get up and running with Midje in a Clojure project and define some tests for a sample project.

Setting up Midje

The easiest way to start using Midje is to install the lein-midje plugin. There are 2 ways to do that. If you’re planning on using Midje in all your projects you can add lein-midje to the :plugins list in the :user profile in ~/.lein/profiles.clj e.g.

{:user
  {:plugins [[lein-midje "2.0.0-SNAPSHOT"]]}}

However if you want to be able to configure this on a project by project basis you can add the lein-midje plugin to the :plugins list in the :dev profile of your project.clj file e.g.

:profiles {
  :dev {
    :dependencies [[midje "1.4.0"]]
    :plugins [[lein-midje "2.0.0-SNAPSHOT"]]}})

The final step is to add midje 1.4.0 (or what ever the latest version is) to the :dev :dependencies as shown above. Once you’ve done that you can run lein midje from the command line within your project to validate everything is setup correctly (if you have any existing tests you should see a message like “All claimed facts (X) have been confirmed.”)

Sample Project

I find the best way to get to grips with a new language or library is to build a small sample project using it. For that reason I’ve put together a simple project to demonstrate some of the features of Midje. As I said the project is really simple, it involves creating a single function that given a number will return that number multiplied by 2. The full source code can be found here, but I’ll be describing the steps needed to recreate the code in the following sections.

So the first thing we need to do is create a new project to work with. Again, the best way to do this is using Lein. The default project template will do nicely so the first step is to go to the directory were you want to create your project and run:

lein new clj-midje-example

Once you’ve done that cd into the project directory and we’re ready to go. We’re going to create a function called times-2 that when given a number returns that number multiplied by 2. Simple, but we’ll expand on it a bit later to demonstrate some of Midje’s features.

First Test

So let’s write our first test. When we created our new project Lein created a sample test case in clj-midje-example/test/clj_midje_example/core_test.clj. We’re going to replace this sample test case with one of our own, but since this test case uses clojure.test we’re going to have to make some other changes as well to get Midje working. So open clj-midje-example/test/clj_midje_example/core_test.clj in an editor and replace the line (:use [clojure.test]) with (:use [midje.sweet]). midje.sweet is the core namespace for Midje and needs to be included in any files containing test cases. Next we need to remove the deftest declaration since we aren’t using clojure.test anymore. Now we’re ready to start writing tests.

Tests in Midje are written as facts. Facts take the following form:

(fact "Doc String"
  (function-to-test params) => expected-result)

Whilst the expected result can be a literal Midje also allows functions such as even?, pos?, nil? etc. Midje provides a number of helper functions you can use to test results, you can read more about them on the Midje Wiki.

For our first test all we really want to do is check that the function does what it’s supposed to do i.e. multiply a single argument by 2, so our test looks like:

(fact "2 * 2 equals 4"
  (times-2 2) => 4)

Add this to core_test.clj and save the file. From the command line run:

lein midje

You should see an error like Exception in thread "main" java.lang.RuntimeException: Unable to resolve symbol: times-2 in this context, compiling:(clj_midje_example/core_test.clj:8). That’s because we haven’t written our times-2 function yet so lets do that.

Write the Code

Open clj-midje-example/src/clj_midje_example/core.clj in an editor and add:

(defn times-2 [a]
  (* 2 a))

and rerun lein midje. This time you should see All claimed facts (1) have been confirmed. Great, the test passes, but the function doesn’t really do much so lets spice things up a bit by making it do a bit more.

Our function as it is right now is great, it takes in a number and multiplies it by 2, simple and effective. But what if we also wanted it to work with vectors and strings ? Let’s say that we wanted a function that if we passed in a string would return the original string repeated e.g. passing in "Hello World!" would return "Hello World!Hello World!". Or if we passed in a vector such as [1 2 3] we would get back [[1 2 3] [1 2 3]] ? Let’s try it.

More Tests

Once again we start by writing some test cases, 1 to test the behaviour for strings and 1 to test the behaviour for vectors. So let’s open clj-midje-example/test/clj_midje_example/core_test.clj in an editor and add:

(fact
  (times-2 [1 2 3]) => [[1 2 3] [1 2 3]])

(fact
  (times-2 "123") => "123123")

Run lein midje again and you should see an error like:

FAIL at (core_test.clj:11)
    Expected: [[1 2] [1 2]]
      Actual: nil

FAIL at (core_test.clj:14)
    Expected: "123123"
      Actual: nil

FAILURE: 2 facts were not confirmed. (But 1 was.)

Now let’s add the code to get the test cases to pass.

Update the Code

We need to modify our function to allow it to also accept strings and vectors in addition to numbers. The easiest way to do that is to check the type of the function’s argument and handle it appropriately. Open clj-midje-example/src/clj_midje_example/core.clj in an editor and replace the times-2 function with the following:

(defn times-2 [a]
  (cond
    (= (type a) java.lang.Long) (* 2 a)
    (= (type a) clojure.lang.PersistentVector) (vec (repeat 2 a))
    (= (type a) java.lang.String) (apply str (repeat 2 a))))

Once again run lein midje and you should see All claimed facts (3) have been confirmed.

Excellent, our function now works exactly as we wanted. Except there’s one small problem, this is completely the wrong way to handle polymorphism in Clojure. The right way would have been to use Protocols or Multimethods. So let’s refactor the code to do it right. To keep things simple we’ll use Multimethods.

Multimethods are a system Clojure provides to support polymorphism. They allow dispatching on types, values, attributes or metadata of function arguments as well as the relationships between the arguments. You define a multimethod using defmulti and provide a dispatching function as part of the definition that is applied to each of the method arguments to determine a dispatching value. That value is then used to determine which method, defined using defmethod, to call. You can also define a default method, using the :default dispatch value, that will be called if no other matching methods can be found. Here’s what the refactored code using Multimethods looks like:

(defmulti times-2 class)

(defmethod times-2 String [a]
  (apply str (repeat 2 a)))

(defmethod times-2 clojure.lang.PersistentVector [a]
  (vec (repeat 2 a)))

(defmethod times-2 :default [a]
  (* 2 a))

So what we’ve done here is define a times-2 method using defmulti and we’ve specified the dispatch function to be class so we can define different behaviour depending on the argument type. We then define 3 times-2 methods using defmethod. The first method will be called if the argument is a String and will return the String repeated twice. The second method will be called if the argument is a vector and will return a vector containing the original vector repeated twice. The third method uses the :default value so will be called whenever the argument is anything but a String or a vector.

So now let’s run our tests again to make sure our refactoring hasn’t changed anything. After running lein midje you should see still All claimed facts (3) have been confirmed.

Conclusion

We’ve only scratched the surface of what you can do with Midje. We haven’t talked at all about how you can group related facts using facts or about how you can define prerequisites using the provided function nor anything about the powerful prepackaged checkers that Midje provides. This is a framework with a lot to it and I’m hoping you can see that whilst Midje is simple to get started with it has a great deal of depth. As always the best place to find out more is the Midje Wiki.

This entry was posted in Clojure and tagged , . Bookmark the permalink.