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 [email protected]
Note that posts from new members are moderated - please be patient with your
first post.
To unsubscribe from this group, send email to
[email protected]
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 [email protected].
For more options, visit https://groups.google.com/groups/opt_out.