简体   繁体   中英

Using with-redefs inside a doseq

I have a Ring handler which uses several functions to build the response. If any of these functions throw an exception, it should be caught so a custom response body can be written before returning a 500.

I'm writing the unit test for the handler, and I want to ensure that an exception thrown by any of these functions will be handled as described above. My instinct was to use with-redefs inside a doseq :

(doseq [f [ns1/fn1 ns1/fn2 ns2/fn1]]
  (with-redefs [f (fn [& args] (throw (RuntimeException. "fail!"))]
    (let [resp (app (request :get "/foo")))))]
      (is (= (:status resp) 500))
      (is (= (:body resp) "Something went wrong")))))

Of course, given that with-redefs wants to change the root bindings of vars, it is treating f as a symbol. Is there any way to get it to rebind the var referred to by f ? I suspect that I'm going to need a macro to accomplish this, but I'm hoping that someone can come up with a clever solution.

with-redefs is just sugar over repeated calls to alter-var-root , so you can just write the desugared form yourself, something like:

(doseq [v [#'ns1/fn1 #'ns1/fn2 #'ns2/fn1]]
  (let [old @v]
    (try
      (alter-var-root v (constantly (fn [& args]
                                      (throw (RuntimeException. "fail!")))))
      (let [resp (app (request :get "/foo")))
        (is (= (:status resp) 500))
        (is (= (:body resp) "Something went wrong")))
      (finally (alter-var-root v (constantly old))))))

Following on from amalloy's great answer , here's a utility function that I wrote to use in my tests:

(defn with-mocks
  "Iterates through a list of functions to-mock, rebinding each to mock-fn, and calls
  test-fn with the optional test-args.

  Example:

      (defn foobar [a b]
        (try (+ (foo a) (bar b))
             (catch Exception _ 1)))

      (deftest test-foobar
        (testing \"Exceptions are handled\"

          (with-mocks
            [#'foo #'bar]
            (fn [& _] (throw (RuntimeException. \"\")))
            (fn [a b] (is (= 1 (foobar a b)))) 1 2)))"
  [to-mock mock-fn test-fn & test-args]
  (doseq [f to-mock]
    (let [real-fn @f]
      (try
        (alter-var-root f (constantly mock-fn))
        (apply test-fn test-args)
        (finally
          (alter-var-root f (constantly real-fn)))))))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM