diff --git a/README.md b/README.md index 4815ace..2054c60 100644 --- a/README.md +++ b/README.md @@ -240,9 +240,7 @@ to mock multiple protocols. ``` You will also find a `spy` macro within the protocol namespace, this -currently supports spying on an implementation for a single protocol. -Caution: the protocol `spy` macro will change in the future when I add support for spying on multiple protocols, you can see examples in the -`spy.protocol-test` namespace. +can also be used to spy on multiple protocols: `(spy.protocol/spy Proto1...ProtoN impl)` ## Contributing diff --git a/src/clj/spy/protocol.clj b/src/clj/spy/protocol.clj index 8581cd6..3e81e86 100644 --- a/src/clj/spy/protocol.clj +++ b/src/clj/spy/protocol.clj @@ -45,20 +45,33 @@ methods)) (defmacro spy - "Reify the protocol, spies attached to the reified object via metadata" + "Reify the protocols, spies attached to the reified object via metadata. + Can accept multiple protocols at once: (spy.protocol/spy Proto1...ProtoN impl)" {:style/indent [:defn [1]]} - [protocol instance] - (let [methods (vals (protocol-methods @(resolve protocol))) + [& args] + (let [protocols (butlast args) + instance (last args) + proto-methods (reduce (fn [acc protocol] + (assoc acc protocol (vals (protocol-methods @(resolve protocol))))) + {} + protocols) + all-methods (->> proto-methods vals (apply concat)) spy-fns-sym (gensym "spy-fns-")] - `(let [~spy-fns-sym ~(->spy-fns methods instance)] + + `(let [~spy-fns-sym ~(->spy-fns all-methods instance)] (with-meta - (reify ~protocol - ~@(mapcat (fn [{:keys [name arglists]}] - (map (fn [arglist] - (let [args (->args arglist)] - (list name args (concat (list (list (keyword name) spy-fns-sym)) args)))) - arglists)) - methods)) + ~@(let [protos+impls + (for [[protocol methods] proto-methods] + (concat + (list (symbol protocol)) + (mapcat (fn [{:keys [name arglists]}] + (map (fn [arglist] + (let [args (->args arglist)] + (list name args (concat (list (list (keyword name) spy-fns-sym)) args)))) + arglists)) + methods)))] + (list (concat '(reify) + (apply concat protos+impls)))) ~spy-fns-sym)))) (defn spies [instance] diff --git a/test/clj/spy/protocol_test.clj b/test/clj/spy/protocol_test.clj index bcf62dc..fbf2b59 100644 --- a/test/clj/spy/protocol_test.clj +++ b/test/clj/spy/protocol_test.clj @@ -100,9 +100,9 @@ methods (protocol-methods p)] (is (= {:hello - {:name 'hello - :arglists [['this 'a] ['this 'a 'b 'c]] - :var #'spy.protocol-test/hello}} + {:name 'hello + :arglists [['this 'a] ['this 'a 'b 'c]] + :var #'spy.protocol-test/hello}} methods))) (let [p @(resolve 'spy.protocol-test/AllSorts) @@ -157,20 +157,40 @@ (method-b [this x])) (deftest multi-protocols-test - (let [mock (protocol/mock - ProtocolA - (method-a [this x] - :a) - - ProtocolB - (method-b [this x] - :b))] - (is (= :a (method-a mock :testing))) - (is (= :b (method-b mock :test))) - (is (spy/called-once-with? (:method-a (protocol/spies mock)) - mock - :testing)) - - (is (spy/called-once-with? (:method-b (protocol/spies mock)) - mock - :test)))) + (testing "mock" + (let [mock (protocol/mock + ProtocolA + (method-a [this x] + :a) + + ProtocolB + (method-b [this x] + :b))] + (is (= :a (method-a mock :testing))) + (is (= :b (method-b mock :test))) + (is (spy/called-once-with? (:method-a (protocol/spies mock)) + mock + :testing)) + + (is (spy/called-once-with? (:method-b (protocol/spies mock)) + mock + :test)))) + (testing "spy" + (let [impl (reify + ProtocolA + (method-a [this x ] :a) + ProtocolB + (method-b [this x] :b)) + spied (protocol/spy ProtocolA ProtocolB impl)] + + (is (= :a (method-a spied :testing))) + (is (= :b (method-b spied :test))) + + (is (spy/called-once-with? (:method-a (protocol/spies spied)) + spied + :testing)) + + (is (spy/called-once-with? (:method-b (protocol/spies spied)) + spied + :test))))) +