1

The with-redefs function appears to be just what is needed to mock/stub out dependencies in clojure tests. I'm using clojure.test [1.10.1]

Initially it gave me a lot of grief, where the new bindings were not applied when I ran the test. Finally I got the following setup to work as expected. The dependencies are required from other namespaces

(ns abc 
  (:require [anotherns.id-gen-mock :as mock])

(deftest test-buy
  (testing "Appends trade to the trades log"
    (let [mock (atom {})]
      (with-redefs [id-gen/get-next-id  mock/get-next-id
                    save-trade          (fn [t] (reset! mock {:trade t}))]
          ... test code

  ))))

Now I realized, that the mocks can be common to all my tests, so I moved it up like so.

(with-redefs [id-gen/get-next-id  mock/get-next-id
              save-trade          identity]
  (deftest test-holdings
    (testing "after 1 buy"
      ... test code
        
        

Now the new bindings are not used, the real dependencies are invoked - failing the test.

I see some posts on SO mentioning something about "direct linking" but I couldn't really fathom why it works in Case1 but not in Case2. If I move with-redefs back under the deftest form, it works again.

Alan Thompson
  • 29,276
  • 6
  • 41
  • 48
Gishu
  • 134,492
  • 47
  • 225
  • 308
  • Is the code you are testing lazy or does it run in different threads? – cfrick Dec 27 '20 at 14:55
  • @cfrick - No threads or lazy seq AFAIK. Just a call to the mocked dependency followed by an atomic collection/map update. – Gishu Dec 27 '20 at 15:33

1 Answers1

5

According to the docstring (https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/with-redefs), with-redefs restores the original value after executing the body. In the given case, the body defines a test. With-redefs thus governs the tests' definition, but not their execution. When the tests execute, any reference to get-next-id resolves that symbol to its current value, which by that time will be the real one, not the mock. (This follows logically: if case No.1 holds, and the docstring holds, then case No.2 could not hold.)

For reasons already mentioned in comments, with-redefs is not usually a tool of first choice. Clojure offers more robust techniques, e.g., make higher-order subsystems and use fixtures to configure them for testing.

Biped Phill
  • 1,181
  • 8
  • 13