On Wednesday, 27 March 2013 08:06:30 UTC+5:30, Leif wrote: > > Hi, Shantanu. > > Thanks for the suggestions. A couple thoughts: > > 1. Many times, I seem to stub or mock things that are scattered here and > there in the code, like things that send email or log metrics, etc. so > they are not really isolated (or isolatable??), but I still want to test > that they get called. > 2. So I'm usually mocking utility functions that have side effects. Am I > mocking at the right level? > 3. For these types of things, even if I make protocols for them, passing > them all in as arguments seems like it could get messy fast. E.g. my > function used to be (f x), but now it's (f x logger emailer metric-logger > ...) > 4. I'm not really familiar with the methods you describe, so any > elaboration on your comments would be welcome. Or even better, a link to a > project that uses the designs you mentioned. >
I do not have any Open Source application using this technique to share now, but I will try to illustrate here. If you have a Ring-based web app, you can initialize the app in a middleware, or if you have a command-line app then you can initialize in the -main function. The initialization function can return a map of mockable implementations, which could be either protocol implementations or functions: (def ^:dynamic *deps* nil) ;; in the Ring middleware or -main fn (using prod config) (let [deps {:logger (make-logger app-config) ; returns fn :emailer (make-emailer app-config) ; returns fn :db-worker (make-db-worker) ; IDatabase protocol impl :metric-logger (make-metric-logger app-config)}] (binding [*deps* deps] ; compojure route handlers can use *deps* ...)) So, how should the regular functions be defined now? We need to propagate the dependencies to all the places that need it. Earlier version: ;; notice the dependencies: logger, db, email (defn foo [x] (util/logger :debug "Enter") (let [p (make-db-payload x)] (db/write p)) (let [m (compose-mail "xyz")] (util/email m)) (util/logger :debug "Exit")) New version: ;; deps can be actual services or mocked (defn foo [x deps] ((:logger deps) :debug "Enter") (let [p (make-db-payload x)] (.write (:db-worker deps) p)) (let [m (compose-mail "xyz")] ((:emailer deps) m)) ((:logger deps) :debug "Exit")) This can be streamlined by extracting snippets like (:logger deps) into dedicated functions to avoid typos. But yeah, the idea is that deps needs to be carried around. In the places where you need to mock something, just call as follows: (let [deps (make-mock-deps)] (foo x deps)) So, to answer your question #2, in this approach the code has an inside-out design and works at a higher order. Shantanu -- -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en --- You received this message because you are subscribed to the Google Groups "Clojure" group. To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/groups/opt_out.