Reputation: 12682
How to mock a specific implementation of a protocol, preferably with plain Clojure i.e. no external libs e.g. Midje? I know how to mock all implementations but not specific ones:
(defprotocol P
(foo [p]))
(defrecord R1 [])
(defrecord R2 [])
(extend-protocol P
R1
(foo [_] :r1)
R2
(foo [_] :r2))
(foo (->R1)) ;; :r1
(foo (->R2)) ;; :r2
;; now the mocking ...
(with-redefs [foo (constantly :redefed)]
(println (foo (->R1))) ;; :redefed
(println (foo (->R2)))) ;; :redefed
i.e. how to get (foo (->R1))
to return :redefed
while (foo (->R2))
still returns :r2
?
Assume I can only make changes below the now the mocking ...
comment. Notice that I am putting aside the recommendation to extend a protocol only if I have control of the protocol, the type, or both.
Upvotes: 0
Views: 622
Reputation: 4368
It might be easiest to define wrapper functions for protocol internal implementation functions and call them from outside.
Then it can be mocked easily with with-redefs
et al.
It also has other benefits, e.g. being able to define specs for such functions.
Upvotes: 1
Reputation: 144136
You could create a new protocol containing methods with the same signature and use that when rebinding:
(def foo-orig foo)
(defprotocol Q
(bar [q]))
(extend-protocol Q
R1
(bar [_] :redefed)
R2
(bar [this] (foo-orig this)))
Note you'll have to capture the original foo
definition for the implementations you don't want to change. Then:
(with-redefs [foo bar]
(println (foo (->R1))) ;; :redefed
(println (foo (->R2)))) ;; :r2
or you could defined a multimethod e.g.
(defmulti bar (fn [q] (type q)))
(defmethod bar R1 [_]
:redefed)
(defmethod bar :default [q]
(foo-orig q))
Upvotes: 1
Reputation: 91992
Don't pass foo
an R1 at all. Define a new implementation, R1Mocked, that does whatever you want, and pass that instead. This is exactly the sort of polymorphism that protocols are designed to support.
Upvotes: 0
Reputation: 29958
My first thought is to have the foo
impls delegate to a helper fn:
(extend-protocol P
R1
(foo [p] (foo-r1 p))
R2
(foo [p] (foo-r2 p)))
with
(defn foo-r1 [_] :r1)
(defn foo-r2 [_] :r2)
and then just redef foo-r1
and foo-r2
independently as desired.
Note also that with-redefs
is meant to operate on var
instances, while the foo
you define as part of a protocol is not the same as a var
. That may be the cause of your global redefinition problem.
You may need to clarify your use-case & update the question. See this part at clojure.org:
Extend only things you control You should extend a protocol to a type only if you control the type, the protocol, or both. This is particularly important for the protocols included with Clojure itself.
Upvotes: 1