From 03c543e08f1eecb15d45d8b8cdf2f2eaa4d4c204 Mon Sep 17 00:00:00 2001 From: Joshua Smock Date: Mon, 19 Jun 2023 17:11:04 +0200 Subject: [PATCH] Fix setup-mocks and with-mocks original binding reinstantiation A regression was caused by #28, such that the original binding values would be called when put back (due to the change to always reinstantiate the mocks for each test case). This wasn't covered by the tests and for non-side-effecting functions, this wouldn't surface, but for non-functions and for side-effecting functions, calling the original function could cause issues. This commit fixes that by wrapping the original binding values in functions just like the mocks. This commit also adds some useful comments and renames binding symbols since every time I look at `with-scoped-redefs` I get confused since the function was basically copied from `with-redefs`, and the naming there is a bit terse. Fixes #44. --- cljest/src/cljest/helpers/core.clj | 36 ++++++++++++++++-------- cljest/src/cljest/helpers/core_test.cljs | 12 +++++++- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/cljest/src/cljest/helpers/core.clj b/cljest/src/cljest/helpers/core.clj index 8f061e3..5bfdcfa 100644 --- a/cljest/src/cljest/helpers/core.clj +++ b/cljest/src/cljest/helpers/core.clj @@ -5,18 +5,32 @@ "Similar to `with-redefs` but allows arbitrarily beginning and ending when the bindings are mocked and reset by calling `start` and `finish` respectively." [start finish bindings & body] - (let [names (take-nth 2 bindings) - vals (take-nth 2 (drop 1 bindings)) - wrapped-vals (map (fn [v] (list 'fn [] v)) vals) - orig-val-syms (for [_ names] (gensym)) - temp-val-syms (for [_ names] (gensym)) - binds (map vector names temp-val-syms) - redefs (reverse (map vector names orig-val-syms)) + (let [original-syms (take-nth 2 bindings) + orig-vals (take-nth 2 (drop 1 bindings)) + + ;; Wrap the values in a function so that they get recreated on each call. This + ;; prevents something stateful from "keeping" its state in multiple test cases. + wrapped-orig-vals (map (fn [v] (list 'fn [] v)) orig-vals) + + ;; Generate new symbols for both the original values and the + ;; temporary values + orig-val-syms (for [_ original-syms] (gensym)) + temp-val-syms (for [_ original-syms] (gensym)) + + ;; Create new vectors that sets the original symbols to the temporary ones + mocked-binds (map vector original-syms temp-val-syms) + + ;; Put the bindings back. Use the same function call method as above to mimic + ;; how we handle mocks + orig-binds (reverse (map (fn [name v] [name (list 'fn [] v)]) original-syms orig-val-syms)) + + ;; The actual `(set! sym (val))`. It calls whatever `v` it, so the values need + ;; to be wrapped in a function to allow for that. bind-value (fn [[k v]] (list 'set! k (list v)))] - `(let [~@(interleave orig-val-syms names) - ~@(interleave temp-val-syms wrapped-vals) - ~start #(do ~@(map bind-value binds)) - ~finish #(do ~@(map bind-value redefs))] + `(let [~@(interleave orig-val-syms original-syms) + ~@(interleave temp-val-syms wrapped-orig-vals) + ~start #(do ~@(map bind-value mocked-binds)) + ~finish #(do ~@(map bind-value orig-binds))] ~@body))) (defmacro setup-mocks diff --git a/cljest/src/cljest/helpers/core_test.cljs b/cljest/src/cljest/helpers/core_test.cljs index e7f7890..30160e4 100644 --- a/cljest/src/cljest/helpers/core_test.cljs +++ b/cljest/src/cljest/helpers/core_test.cljs @@ -31,6 +31,14 @@ (swap! counter dec) @counter))) + ;; The presence of this function validates mocking the symbol + ;; doesn't cause the function to be called. See https://github.com/pitch-io/cljest/issues/44 + ;; The same could be done with a primitive value that can't be `.call`-ed, but this + ;; will show a nicer error. + (defn ^:private my-super-cool-fn + [] + (throw (js/Error. "This function should never be called!"))) + (h/setup-mocks [something-stateful (let [counter (atom 0)] (fn [] (swap! counter (partial + 2)) @@ -39,7 +47,9 @@ something-else-stateful (let [counter (atom 0)] (fn [] (swap! counter #(- % 2)) - @counter))]) + @counter)) + + my-super-cool-fn (constantly nil)]) (it "works" (is (= 2 (something-stateful)))