如何模拟协议的特定实现?

how to mock a specific implementation of a protocol?

如何模拟协议的特定实现,最好使用普通的 Clojure,即没有外部库,例如米杰?我知道如何模拟 所有 实现但不知道特定的实现:

(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

即如何将 (foo (->R1)) 变为 return :redefed(foo (->R2)) 仍然是 returns :r2?

假设我只能在 now the mocking ... 评论下方进行更改。请注意,我搁置了仅当我可以控制协议、类型或两者的情况下才扩展协议的建议。

我的第一个想法是让 foo impls 委托给助手 fn:

(extend-protocol P
  R1
  (foo [p] (foo-r1 p))
  R2
  (foo [p] (foo-r2 p)))

(defn foo-r1 [_] :r1)
(defn foo-r2 [_] :r2)

然后根据需要独立重新定义 foo-r1foo-r2

另请注意,with-redefs 旨在对 var 个实例进行操作,而您定义为协议一部分的 foovar 不同.这可能是您的全局重新定义问题的原因。


更新

您可能需要澄清您的用例并更新问题。见 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.

根本不要通过 foo R1。定义一个新的实现,R1Mocked,它可以做任何你想做的事,然后传递它。这正是协议旨在支持的多态性。

您可以创建一个包含具有相同签名的方法的新协议,并在重新绑定时使用它:

(def foo-orig foo)

(defprotocol Q
  (bar [q]))
(extend-protocol Q
  R1
  (bar [_] :redefed)
  R2
  (bar [this] (foo-orig this)))

请注意,您必须为不想更改的实现捕获原始 foo 定义。那么:

(with-redefs [foo bar]
              (println (foo (->R1)))   ;; :redefed
              (println (foo (->R2))))  ;; :r2

或者您可以定义一个多方法,例如

(defmulti bar (fn [q] (type q)))
(defmethod bar R1 [_]
  :redefed)
(defmethod bar :default [q]
  (foo-orig q))

为协议内部实现函数定义包装函数并从外部调用它们可能是最简单的。 然后它可以很容易地被 with-redefs 等人嘲笑。 它还具有其他好处,例如能够为此类功能定义规范。