Sorry, in the last illustration, the (binding [*deps* deps] ...) cannot be useful for Compojure route handlers because dynamic vars are bound at a thread-local level; you will probably have to `alter-var-root` it to some var and have the handlers use that static var instead. In the code I write personally, I use a static var that I `alter-var-root` so I couldn't see the error in dynamic var immediately.
Shantanu On Wednesday, 27 March 2013 12:16:58 UTC+5:30, Shantanu Kumar wrote: > > > > 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.