maybe a different approach could be to use a richer datatype than a function, which carries both: the command and the undo command.
(deftype Command [action undo])
Then you could do something like:
(defn do-patch!
[command args]
(dosync
(let [patch {:command command :args (vec args)}]
(apply (.action command) args)
(alter patches- conj patch)
patch)))
(defn undo-patch!
[]
(dosync
((.undo (:command patch)))
(alter patches- pop)))
Then you could provide specially crafted Commands and there would not be the need to stub anything. Also Commands wouldn't have to be global objects.
Sincerely
Meikel
(ns midje.util.git
(:use [midje.sweet]))
;; Code except for undo-patch is the same as before.
;; ...
;; I pulled remove-patch out of undo-patch because I was
;; getting a screwy read error I didn't want to figure out
;; at 5 in the morning, but I'd probably do that anyway:
;; http://codebetter.com/jeremymiller/2006/12/03/composed-method-pattern/
(defn remove-patch [patch]
(alter patches- #(remove (fn [p] (= patch p)) %)))
(defn undo-patch [patch]
(let [fn (undo-fn patch)]
(dosync
(fn)
(remove-patch patch))))
(fact "The patch's undo-fn is called for its side effect and the patch is forgotten"
(let [visible-evidence-of-a-side-effect (atom nil)]
(undo-patch ...patch...) => anything
(provided
(undo-fn ...patch...) => (fn [] (reset! visible-evidence-of-a-side-effect :happened!))
(remove-patch ...patch...) => :nothing-of-interest)
@visible-evidence-of-a-side-effect => :happened!))
1855 $ lein midje midje.util.git
All claimed facts (2) have been confirmed.
1856 $
-----
Brian Marick, Artisanal Labrador
Contract programming in Ruby and Clojure
Author of /Ring/ (forthcoming; sample: http://bit.ly/hfdf9T)
www.exampler.com, www.exampler.com/blog, www.twitter.com/marick
> The issue is where do I specify that:
> (undo-fn ...patch...) => (fn [] (reset! visible-evidence-of-a-side-
> effect :happened!))
The code you quoted is that specification. It doesn't matter that undo-fn is a multimethod.
Here's what the notation of the test says:
When called with an arbitrary patch, undo-patch will produce a particular side effect. It does that because it uses undo-patch, which--when given that arbitrary patch--returns a function that produces that side effect.
It also calls remove-patch with the given patch.
undo-patch can return anything it wants. We don't care.
>
> (fact "The patch's undo-fn is called for its side effect and the patch is forgotten"
> (let [visible-evidence-of-a-side-effect (atom nil)]
> (undo-patch ...patch...) => anything
> (provided
> (undo-fn ...patch...) => (fn [] (reset! visible-evidence-of-a-side-effect :happened!))
> (remove-patch ...patch...) => :nothing-of-interest)
> @visible-evidence-of-a-side-effect => :happened!))
I'm biased, but I think Midje is ready for production use. There's a smooth migration path because Midje uses the same reporting as clojure.test, so you can mix styles in the same file and still use (for example) lein test to see if anything failed. (I'll go write up a Migrating page in the wiki after I send this mail.)
There are things clojure.test does that Midje doesn't:
* Named tests (deftest). So you can't write runner functions like this:
(deftest arithmetic-tests
(subtraction-tests)
(addition-tests))
unless you also :use clojure.test and wrap the Midje facts in deftests (which works fine).
* There's no equivalent to with-test.
* There's no #'are, though I suspect using checker functions would work as well.
Midje isn't a superset of the features of other clojure.test alternatives. For example, it doesn't have the auto-runner that LazyTest does, and it doesn't have the trimmed stack traces of Expectations. I plan to keep stealing ideas, though.