Dynamic vars in ring handlers

At work we have some tests that spin up various jetty instances that return sample test data. We use these to mock out other services on our platform and switch the app's config at test time to point at them instead of the real services. It's actually a pretty great set up that I plan to talk about at a later date but a specific issue came up today I'd like to address first. We have these mock services spun up in a clojure.test fixture and we were wondering if we could have one of the tests assert directly in our mock services ring handler. Something like this.

(deftest call-foobar
"make sure when we call foobar the foo
 header is set to bar"
 (let [server (jetty/run-jetty 
               (fn [r] (is (= (get "foo" (:headers r)) "bar")))
               {:port 9999 :join? false})]
   (try
     (call-foobar-function {:some :data})
     (finally
       (.stop server))))) 

The question we had was can we call this is function directly in the ring handler? To understand if this is possible or not we have to understand how deftest works and how it collects it's results.

The way deftest works is that it stores the result of the various is calls in a dynamic var called *report-counters*. Dynamic vars are vars that can be rebound. What's important is that typically (and the way clojure.test does it) this rebinding is thread local. It is done using the binding macro.

A few example tests show that this is a problem in our case because jetty uses different threads to respond to the requests.

user=> (use 'ring.adapter.jetty)  
nil  
user=> (use 'clj-http.client)  
nil  
user=> (def ^:dynamic *foobar* :notbound)  
#'user/*foobar*
user=> *foobar*  
:notbound
user=> (binding [*foobar* :bound] *foobar*)  
:bound
user=> (def app1 (run-jetty (fn [x] (println *foobar*)) {:port 8080 :join? false}))  
#'user/app1
IPM-jobs.web=> (binding [*foobar* :bound] (def app2 (run-jetty (fn [x] (println *foobar*)) {:port 8081 :join? false})))  
#'user/app2
user=> (get "http://localhost:8080")  
:notbound
(long 404 error)
user=> (get "http://localhost:8081")  
:notbound
(long 404 error)

As you can see, the binding is not in scope for the ring handler's function and the value is still the default. Because of this the clojure.test *report-counters* var is also not in scope for the test run's bindings and it would not get recorded in the test.

There is however a way to get it to work. Clojure has what are called bound functions which carry with them the bindings that exist when they are created. With bound-fn we can create a ring handler that uses the *report-counters* var that's in scope of the test run and everything will work as expected. Here's an example like the repl testing we were doing above.

user=> (binding [*foobar* :bound] (def app3 (run-jetty (bound-fn [x] (println *foobar*)) {:port 8082 :join? false})))  
#'user/app3
user=> (get "http://localhost:8082")  
:bound