How can I use with-redefs to mock multiple calls to the same function?

Viewed 648

I would like to be able to mock MyFunction however I need the mock to return different values when MyFunction is called.

Is it possible to use with-redefs to return different values based on the call order of a function?

(testing "POST /foo/bar and return ok"
  (with-redefs [->Baz (fn [_]
                    (reify MyProtocol (MyFunction [_] [{:something 1}]))
                    (reify MyProtocol (MyFunction [_] [{:something 2}])))]

    (let [response (routes/foo {:request-method :post
                            :uri            "/foo/bar"
                            :query-params   {}
                            })]

      (is (= (:status response) 200)))))
2 Answers

You could use a mutable collection of the return values, then return/remove values from it on each call.

(defn foo [x] (inc x)) ;; example fn to be mocked

If you wanted to mock three calls to foo returning 1, 2, and 3 respectively:

(with-redefs [foo (let [results (atom [1 2 3])]
                    (fn [_] (ffirst (swap-vals! results rest))))]
  (prn (foo 0))
  (prn (foo 0))
  (prn (foo 0))
  ;; additional calls would return nil
  (prn (foo 0)))
;; 1
;; 2
;; 3
;; nil

That uses swap-vals! to get the old/new values of the atom, but requires Clojure 1.9 or greater.

If you don't have swap-vals! you could do it (less atomically) like this:

(with-redefs [foo (let [results (atom [1 2 3])]
                    (fn [_]
                      (let [result (first @results)]
                        (swap! results rest)
                        result)))]
  ...)

We use Picomock for this, and to assert on the parameters for each call, and to assert on the number of calls. Recommended!

Related