TDD in clojure with midje

In our previous article we wrote the tests using the clojure.test library. This library is fine for simple tests but is somewhat lacking when it comes to support of more advanced stuff like testing collaborators (function a calls function b and things like that). In this article we will see how midje is a good alternative to clojure.test and has a better support for TDD than clojure.test.

Test doubles: Dummies, Fakes, Stubs and Mocks

I will use the terminology from “Mocks aren’t Stubs” by Martin Fowler (which he took from Gerard Meszaros’s xUnit patterns book). In his article, Fowler uses the following terms:

  • Dummy objects are passed around but never actually used
  • Fake objects have an implementation but is not the “real deal” (like an inmemory database)
  • Stubs provide canned answers
  • Mocks have a pre-programmed set of expectations of what should be called on them

We don’t have objects in clojure, but we have very similar things:

  • Dummies like a function, sequence, etc. that is never used
  • Fake functions/macros that have an implementation of some behavior just for testing
  • Stub functions/macros that return a canned result
  • Mock functions/macros that can record the invocations and check them against a set of expectations

Of all them, dummy stuff is very easy to use in clojure.test as it is never used but things are different when it comes to fakes, stubs and mocks. Macros are a different beast as we have to take into account things like the difference between macro expansion time and execution time. This is why we are going to focus on testing functions.

Fake and stub functions

Let’s assume that we want to test a function ‘controllerGetTask’ that calls another function ‘dbGetTask’. With clojure test, we can use bindings to change the behavior of a given function to a fake or a stub one easily:

(defn db-get-task [id] (str "task " id " from the db")) 
(defn controller-get-task [id] (dbGetTask id))
(binding [db-get-task #(str "stubbed value for " %)]
	(controller-get-task 6))

user=> "stubbed value for 6"

In this piece of code, when controllerGetTask calls dbGetTask it will use the anonymous function from the binding instead of the “real” function.

Mock functions

When it comes to mocks, though, things start to get complicated. We could easily wrap our functions with a recording function (using the same binding mechanism) but the amount of custom code is just too much and the readability of our tests will be very poor. This is the point at which we decided that we needed something else for our tests, and then we found midje.

Midje

Midje is a test framework for clojure that is very easy to use, complete and well documented. In midje, tests are expressed as facts:

(fact "An URI that does not exist should return a 404"
  (:status (request :get "/somefakeuri")) => 404)

So far nothing that cannot be done with clojure.test (although I certainly like the midje style more than clojure.test’s). One of the interesting things about midje is that it is very easy to express prerequisites (things that are supposed to be true if our fact is to be true) like the fact that calling some function returns a certain value (does it remind you of stubs? sure it does)

(fact 
  (:body (controllers/task-list)) => [{:id "whatever" :task "Test the app"}]
    (provided (models/task-list) => [{:id "whatever" :task "Test the app"}]))

We can also have prerequisites with mock semantics as they can record the invocations and express conditions based on them (like counting how many times a funcition has been called):

(fact (:body (task-list)) => [{:id "whatever" :task "Test the app"}]
  (provided (models/task-list) => [{:id "whatever" :task "Test the app"}] :times 1))

In this case, midje will complain either if models/task-list has been called more than once or if it has not been called at all. It is also possible to express in a prerequisite the parameters that are to be passed:

(fact
  (request :post "/api/1/tasklist/0/done") => :whatever
  (provided (task-completed "0") => :whatever :times 1 ))

If we are not interested in the return value (i.e. we are not stubbing the function) we can return the special value ‘anytihing’ (either as the value to check, the parameter of the prerequisite or the return value of the stubbed call):

(fact
  (request :post "/api/1/tasklist/0/done") => anything
  (provided (task-completed "0") => :whatever :times 1 ))

If we compare the midje code with the clojure.test + binding code I think that midje makes our tests much more expressive and much easier to write. It also supports better the TDD cycle (with things like prerequisites or future-facts). As far as my personal experience goes, I have enjoyed a lot more writing midje tests than writing clojure.test test and this is why I strongly recommed you to start writing your tests with midje if you still have not.

Writing and running the tests

In order to start using midje (with leiningen) we need to add the dependency to midje in our project.clj

:dependencies 
	[midje "1.3.0"]
:dev-dependencies
	[lein-midje "1.0.7"]

and then require/use midje.sweet in our test cases:

(ns todo-clojure.test.controllers
  (:use [todo-clojure.controllers])
  (:use [midje.sweet])
  (:require [todo-clojure.models :as models]))
	
(fact "Should return the task list from the model"
  (:body (task-list)) => [{:id "whatever" :task "Test the app"}]
  (provided (models/task-list) => [{:id "whatever" :task "Test the app"}] :times 1))

If we want to run our tests, we can use lein test as every fact in midje is actually a clojure.test or we can use lein midje in order to benefit from midje’s test runner (and superior reporting).

blog comments powered by Disqus