如何模拟协议的特定实现,最好使用简单的Clojure,即没有外部库,例如Midje?我知道如何模拟所有实现,但不模拟特定实现:
(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))
返回:redefed
而(foo (->R2))
仍返回:r2
?
假设我只能在now the mocking ...
注释下方进行更改。请注意,只有在我可以控制协议,类型或两者都有的情况下,我才不建议扩展协议。
答案 0 :(得分:1)
我的第一个想法是让foo
将委托委托给助手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-r1
和foo-r2
。
还要注意,with-redefs
是要在var
实例上运行,而您定义为协议一部分的foo
与var
是不同的。这可能是造成您全球重新定义问题的原因。
您可能需要澄清用例并更新问题。参见this part at clojure.org:
仅扩展您控制的内容您应该将协议扩展为一个类型 仅当您控制类型,协议或同时控制两者时。这是 对于Clojure本身随附的协议特别重要。
答案 1 :(得分:1)
您可以创建一个包含具有相同签名的方法的新协议,并在重新绑定时使用该协议:
time.Duration
请注意,对于您不想更改的实现,您必须捕获原始的(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
答案 2 :(得分:1)
为协议内部实现函数定义包装函数并从外部调用它们可能是最简单的。
然后可以使用with-redefs
等人轻松地模拟它。
它还具有其他好处,例如能够定义此类功能的规范。
答案 3 :(得分:0)
完全不要通过foo
的R1。定义一个新的实现R1Mocked,它可以执行您想要的任何事情,并传递给它。这正是协议旨在支持的多态性。