Skip to content

Commit

Permalink
Fix setup-mocks and with-mocks original binding reinstantiation
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jo-sm committed Jun 19, 2023
1 parent 0de19ae commit 03c543e
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 12 deletions.
36 changes: 25 additions & 11 deletions cljest/src/cljest/helpers/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 11 additions & 1 deletion cljest/src/cljest/helpers/core_test.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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)))
Expand Down

0 comments on commit 03c543e

Please sign in to comment.