beluchin
beluchin

Reputation: 12682

how to mock a specific implementation of a protocol?

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

Answers (4)

Juraj Martinka
Juraj Martinka

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

Lee
Lee

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

amalloy
amalloy

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

Alan Thompson
Alan Thompson

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.


Update

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

Related Questions