Mocking out in Clojure with with-redefs-fn (no Midje this time)

I made some changes in a Clojure project – librarian-clojure. The changes revolved around security in a Compojure web application. What I needed was to check whether they’re correct and hence I needed some tests with mocking involved because the functions the changes were introduced to used external sources – MongoDB and an encryption library. I didn’t mean to set up the environment, but just enough to get the task done. And it should be fast and quick.

The functions are used in a web application, but neither a web container nor a Request object was necessary since Ring turns HTTP requests into…maps. Moreover, I knew Clojure could replace function definition (a var) on the fly for a single thread with binding, but Clojure 1.3 comes with the with-redefs-fn function that “Temporarily redefines Vars during a call to func. (…) Useful for mocking out functions during testing.” I didn’t have to define a dependency and have a framework to work with. Life couldn’t have been easier!

I’ve been doing Java programming for years, but only recently have I been exposed to the aspects of Test-Driven Development (TDD). I think I know the benefits and the rules, but applying TDD was more a belief in its added value not a way to think. I simply didn’t care (perhaps because I wasn’t paid for programming and was only doing it in my spare time, often sidetracked by other activities).

I’ve also been leaning quite recently towards believing that despite repeated assurances of TDD applicability to and increased quality of applications developed in Java or any object-oriented languages, TDD was simply too laborious.

I think that TDD in Clojure (or any functional language) is so much easier and…fun!

Firstly, there’s a REPL where you can do it interactively. Secondly, Clojure’s dynamic. Thirdly, there’s no “overhead” with object-oriented hierarchy since “weakly” structures like maps suffice. All these seem to suggest that Clojure may easily be the language of choice for TDD.

Have a look at the following session in Clojure’s REPL to experience it yourself. Even thought it’s far from TDD, it led to using the fake functions with no pain (and eventually I found out that Midje might’ve done it better. Next time.)

user=> (use 'librarian-clojure.security)
nil
user=> (def request {:params {:login "jacek" :password "secr3t"}})
#'user/request
user=> (log-in request)
AssertionError Assert failed: (somnium.congomongo/connection? c__793__auto__)  librarian-clojure.db/db-get-user (db.clj:63)
user=> ;; no CongoDB up and running - mock its availability
user=> (def ^:dynamic db-get-user)
#'user/db-get-user
user=> (binding [db-get-user (fn [v] (println "user: " v) v)]
         (log-in request))
AssertionError Assert failed: (somnium.congomongo/connection? c__793__auto__)  librarian-clojure.db/db-get-user (db.clj:63)
user=> ;; it doesn't seem to work for another namespace's vars - a bit of Google'ing and with-redefs-fn shows up
user=> (with-redefs-fn {#'librarian-clojure.db/db-get-user (fn [v] (println "user: " v) {:password "secr3t" :roles #{:admin}})
                        #'librarian-clojure.security/check-password (fn [cd cr] (println "candidate: " cd " crypt: " cr) true)}
              #(librarian-clojure.security/log-in request))
user:  jacek
candidate:  secr3t  crypt:  secr3t
{:name "jacek", :roles #{:admin}}
Be Sociable, Share!
This entry was posted in Languages.

Leave a Reply

%d bloggers like this: