in which public speaking is foreshadowed

I was invited to speak at this year's Conj, the first Clojure conference. My talk is titled "Making Leiningen Work for You":

Everyone is probably familiar with the basic Leiningen workflow: new, deps, test, swank, etc. But there's more to our resourceful friend than meets the eye. Learn how to customize Leiningen for your project and how to teach it new tricks through plugins.

I'll be speaking on lesser-known new features, plugin development, and the things that make Leiningen the most-contributed-to Clojure project. As a little teaser about plugin development using Robert Hooke, here's a plugin I wrote yesterday.

(ns rodney.leonard.stubbs
  "Replace all vars in all namespaces  with their :stub metadata."
  (:use [robert.hooke :only [add-hook]]
        [leiningen.compile :only [eval-in-project]]))

(def stubbery
  (fn [f# & args#]
     (doseq [n# (all-ns)
             [_# v#] (ns-publics n#)
             :when (:stub (meta v#))]
       (alter-var-root v# (fn [f# v#] (with-meta (:stub (meta v#))
                                       (assoc (dissoc (meta v#) :stub)
                                         :stubbs/original f#))) v#))
     (apply f# args#)))

(defn add-stub-form [eval-in-project project form & [handler]]
  (let [form `(do (require '~clojure.test)
                  (require '~'robert.hooke)
                  (#'robert.hooke/add-hook #'~'clojure.test/run-tests
                                           ~stubbery)
                  ~form)]
    (eval-in-project project form handler)))

(add-hook #'eval-in-project add-stub-form)

With this in place you can kick up some action.

(ns rodney.test-stubbs
  (:use [clojure.test]))

(defn ^{:stub (constantly true)} stubbed? []
  false)

(deftest test-stubbed
  (is (stubbed?)))
The Man with the Blood on his Hands

This test passes because stubbed? gets replaced with (constantly true) at test time. The idea is that functions which may rely on external services can easily be replaced with stubs that return hard-coded data in order to make your tests run faster and more reliably. Of course, you should couple this with an set of integration tests that use the original definitions; for this purpose, there's a :stubbs/original entry added to the var's metadata containing the original value of the function that's been replaced.

The plugin uses Robert Hooke's add-hook to modify the behaviour of Leiningen's built-in eval-in-project function. All code that runs in your project goes through this function. Inside your project's code, it wraps clojure.test/run-tests in order to search for all vars that have :stub metadata and call alter-var-root on them to stub out their behaviour. It's a little more awkward than most Robert Hooke usage since there's a lot of use of auto-gensym and quote-unquoting involved in order to work around the fact that the code is constructed in a Leiningen plugin but intended to run inside your project's process, but it's a good testament to the flexibility that metadata and hooks offer.

« older | 2010-09-29T04:18:59Z | newer »