0

I have an async Pedestal interceptor I want to test:

(def my-interceptor
  (io.pedestal.interceptor/interceptor
    {:name  :my-interceptor
     :enter (fn [context]
              (as/go
                (do
                  (Thread/sleep 1000)
                  (assoc context :answer 42))))}))

I first tried a naïve test:

(deftest my-test
  (is (= 42
         (:answer (io.pedestal.interceptor.chain/execute {} [my-interceptor])))))

This doesn’t work because chain/execute returns nil when it has async interceptors. I tried another solution adding the test in an interceptor just after the tested one:

(deftest my-test
  (io.pedestal.interceptor.chain/execute
    {}
    [my-interceptor
     (io.pedestal.interceptor/interceptor
       {:name  :test
        :enter (fn [context]
                 (is (= 41 (:answer context))) ; should fail
                 context)})]))

However this doesn’t work because the test terminates before the test is executed, and thus succeeds… even if the test fails a second after:

Ran 1 test containing 0 assertions.
No failures.

FAIL in (my-test) (somefile_test.clj:49)
expected: (= 41 (:answer context))
  actual: (not (= 41 42))

In practice my tests suite (using Kaocha) fails because there’s a deftest with no assertion in it.

Given that chain/execute returns nil and not a chan, I can’t wrap it in a as/<!! to block until it terminates.

At this point I’m stuck. Is there anything I can do to test that kind of interceptors?

bfontaine
  • 18,169
  • 13
  • 73
  • 107
  • Pedestal interceptors are already run in an async manner. Can you add why you want async in an intc? I'm suspecting the best answer is to remove all async behavior and let the framework handle it, for example as it does with XHR calls. – Alan Thompson Apr 10 '20 at 18:07
  • some answers here for how to test async code: https://stackoverflow.com/questions/30766215/how-do-i-unit-test-clojure-core-async-go-macros – Jochen Bedersdorfer Apr 10 '20 at 18:53
  • @JochenBedersdorfer Thanks, I already know how to test async code, but the issue with Pedestal is it doesn’t return a chan I can block-read using `<!!`. – bfontaine Apr 11 '20 at 23:12
  • @AlanThompson they’re run in an async manner only if they return a chan. My async interceptors rely on async controller functions that perform multiple API calls in parallel; I can’t just remove the async behavior. – bfontaine Apr 11 '20 at 23:19
  • your test code could provide the surrounding go block, do a blocking read and after you test with `(= (is...)``, you put something on the channel. – Jochen Bedersdorfer Apr 12 '20 at 00:24
  • Related to https://stackoverflow.com/questions/30766215/how-do-i-unit-test-clojure-core-async-go-macros – Biped Phill Apr 12 '20 at 02:03

1 Answers1

2

How about this approach?

(require '[clojure.test :as test])
(require '[clojure.core.async :as async)

(test/deftest async-test []
 (let [c (async/chan)]
      (future (println "Running mah interceptors") (async/>!! c :done))
      (test/is (= :done (async/<!! c)))
      (async/close! c)))

That runs the actual interceptor code in a separate thread. Test code just needs to post something to the c when it is done.

Jochen Bedersdorfer
  • 4,093
  • 24
  • 26