Fork me on GitHub
#pedestal
<
2023-08-17
>
simon18:08:20

Hi all! I'm struggling a bit testing my pedestal code.. I have an interceptor on a single route which I don't want to run in my tests. How would I go about this? Copy/paste my service-map in the tests and just exclude the interceptor? Or is there a more elegant way to handle this? Thanks for any feedback!

hlship20:08:22

I can think of a few options. First off, you must have a function somewhere that builds your routes and assembles the service map, so you could pass in a test mode (vs. production mode) flag that would skip certain interceptors. Option two: the service map or route starts as data that could be manipulated before the Router is created from the routing data, or the server is created from the service map. Option three: there may be a style of coding the interceptor where you can split out the logic; for example, if the interceptor invokes a function, you could with-redefs change that function to prevent the interceptor from interfering with your test. Maybe even something like:

(with-redefs [interceptor-on-enter-fn identity] .
  .. test code ... ) 

simon20:08:42

Thanks for your feedback! 😊 Option one is a good idea, haven't thought of it. I've tried option two and three, but both didn't work and it was driving me a bit up the walls.. For option two I wrote two functions to manipulate the raw service map. One to remove interceptors from ::io.pedestal.http/interceptors and another to remove interceptors from routes. And even though when I inspected the service map the interceptor on the route was gone, when I started the service it was still applied. 🙈 Could be that it was too long a day in front of the screen though.. I've also tried option three, pretty much like you write, using with-redefs and using the function that is bound to the :enter of the interceptor. But also this didn't work, and I gave up pretty quickly. I just remember from python that it depends very much in which namespace you mock/redef the function, and so didn't try a lot. Option three would be my favorite though, if I get it to work. I have an interceptor namespace where I define my custom interceptors and create them like this:

(def presence-interceptor
  (io.pedestal.interceptor/interceptor
    {:name ::presence-interceptor
     :enter update-presence}))
Then in my tests I import the interceptor and redefine it:
(ns core-test
  (:require
    [app.interceptors :as interceptors]
    [io.pedestal.test :as test]))

(def service (:io.pedestal.http/service-fn (io.pedestal.http/create-servlet core/service-map)))

(deftest test-something
  (with-redefs [interceptors/update-presence identity]
    (is (= "" (:body (test/response-for service
                                :post "/test/"
                                :headers {"Content-Type" "application/json"}
                                :body "{}"))))
But in my tests this didn't work. I've also tried creating my service within the scope of with-redefs, but didn't work either. Does this look correct, like how you would use redef here with the interceptor?

hlship22:08:17

If you made it ... :enter #'update-presence it may work. What's happening is that you are capturing the value of the update-presense Var, a fn. Later, you with-redefs to rebind the Var, but the captures function is still in use. By always invoking the Var (not capturing it), you incur the cost of de-referencing the value of the Var, but then are able to rebind it for your tests. I should have mentioned that here, there's some more context here: http://pedestal.io/guides/live-repl