I'd like to mock out a macro in a namespace.
For instance, clojure.tools.logging/error
.
I tried with-redefs
with no luck
(def logged false)
(defmacro testerror
{:arglists '([message & more] [throwable message & more])}
[& args]
`(def logged true))
(deftest foo
...
(with-redefs
[log/error testerror]
...
That gave this error:
CompilerException java.lang.RuntimeException: Can't take value of a macro
Amalloy provided you the answer for your direct question on how to mock a macro - you cannot.
However, you can solve your problem with other solutions (simpler than moving your whole application to component dependency injection). Let me suggest two alternative implementations (unfortunately, not very straightforward but still simpler than using component).
You cannot mock a macro but you can mock a function that will be used when the logging macro get expanded.
(require '[clojure.tools.logging :as log])
(require '[clojure.pprint :refer [pprint]])
(pprint (macroexpand `(log/error (Exception. "Boom") "There was a failure")))
Gives:
(let*
[logger__739__auto__
(clojure.tools.logging.impl/get-logger
clojure.tools.logging/*logger-factory*
#object[clojure.lang.Namespace 0x2c50fafc "boot.user"])]
(if
(clojure.tools.logging.impl/enabled? logger__739__auto__ :error)
(clojure.core/let
[x__740__auto__ (java.lang.Exception. "Boom")]
(if
(clojure.core/instance? java.lang.Throwable x__740__auto__)
(clojure.tools.logging/log*
logger__739__auto__
:error
x__740__auto__
(clojure.core/print-str "There was a failure"))
(clojure.tools.logging/log*
logger__739__auto__
:error
nil
(clojure.core/print-str x__740__auto__ "There was a failure"))))))
As you can see, the function that does actual logging (if a given level is enabled) is done with clojure.tools.logging/log*
function.
We can mock it and write our test:
(require '[clojure.test :refer :all])
(def log-messages (atom []))
(defn log*-mock [logger level throwable message]
(swap! log-messages conj {:logger logger :level level :throwable throwable :message message}))
(with-redefs [clojure.tools.logging/log* log*-mock]
(let [ex (Exception. "Boom")]
(log/error ex "There was a failure")
(let [logged (first @log-messages)]
(is (= :error (:level logged)))
(is (= "There was a failure!" (:message logged)))
(is (= ex (:throwable logged))))))
Your logging library API might provide features that would allow you to plug into in your test to collect and assert logging events. For example with java.util.logging you can write your own implementation of Handler that would collect all logged log records and add it to a specific (or root) logger.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With