rlm@10: diff a/src/main/clojure/clojure/contrib/mock.clj b/src/main/clojure/clojure/contrib/mock.clj (rejected hunks) rlm@10: @@ -1,285 +1,282 @@ rlm@10: -;;; clojure.contrib.mock.clj: mocking/expectation framework for Clojure rlm@10: - rlm@10: -;; by Matt Clark rlm@10: - rlm@10: -;; Copyright (c) Matt Clark, 2009. All rights reserved. The use rlm@10: -;; and distribution terms for this software are covered by the Eclipse rlm@10: -;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php). rlm@10: -;; By using this software in any fashion, you are rlm@10: -;; agreeing to be bound by the terms of this license. You must not rlm@10: -;; remove this notice, or any other, from this software. rlm@10: -;;------------------------------------------------------------------------------ rlm@10: - rlm@10: -(comment rlm@10: - ;; This is a simple function mocking library I accidentally wrote as a side rlm@10: - ;; effect of trying to write an opengl library in clojure. This is loosely rlm@10: - ;; based on various ruby and java mocking frameworks I have used in the past rlm@10: - ;; such as mockito, easymock, and whatever rspec uses. rlm@10: - ;; rlm@10: - ;; expect uses bindings to wrap the functions that are being tested and rlm@10: - ;; then validates the invocation count at the end. The expect macro is the rlm@10: - ;; main entry point and it is given a vector of binding pairs. rlm@10: - ;; The first of each pair names the dependent function you want to override, rlm@10: - ;; while the second is a hashmap containing the mock description, usually rlm@10: - ;; created via the simple helper methods described below. rlm@10: - ;; rlm@10: - ;; Usage: rlm@10: - ;; rlm@10: - ;; there are one or more dependent functions: rlm@10: - rlm@10: - (defn dep-fn1 [] "time consuming calculation in 3rd party library") rlm@10: - (defn dep-fn2 [x] "function with undesirable side effects while testing") rlm@10: - rlm@10: - ;; then we have the code under test that calls these other functions: rlm@10: - rlm@10: - (defn my-code-under-test [] (dep-fn1) (dep-fn2 "a") (+ 2 2)) rlm@10: - rlm@10: - ;; to test this code, we simply surround it with an expect macro within rlm@10: - ;; the test: rlm@10: - rlm@10: - (expect [dep-fn1 (times 1) rlm@10: - dep-fn2 (times 1 (has-args [#(= "a" %)]))] rlm@10: - (my-code-under-test)) rlm@10: - rlm@10: - ;; When an expectation fails during execution of the function under test, rlm@10: - ;; an error condition function is called with the name of the function rlm@10: - ;; being mocked, the expected form and the actual value. These rlm@10: - ;; error functions can be overridden to allow easy integration into rlm@10: - ;; test frameworks such as test-is by reporting errors in the function rlm@10: - ;; overrides. rlm@10: - rlm@10: - ) ;; end comment rlm@10: - rlm@10: -(ns clojure.contrib.mock rlm@10: - ^{:author "Matt Clark", rlm@10: - :doc "function mocking/expectations for Clojure" } rlm@10: - (:use [clojure.contrib.seq :only (positions)] rlm@10: - [clojure.contrib.def :only (defmacro-)])) rlm@10: - rlm@10: - rlm@10: -;;------------------------------------------------------------------------------ rlm@10: -;; These are the error condition functions. Override them to integrate into rlm@10: -;; the test framework of your choice, or to simply customize error handling. rlm@10: - rlm@10: -(defn report-problem rlm@10: - {:dynamic true} rlm@10: - ([function expected actual] rlm@10: - (report-problem function expected actual "Expectation not met.")) rlm@10: - ([function expected actual message] rlm@10: - (prn (str message " Function name: " function rlm@10: - " expected: " expected " actual: " actual)))) rlm@10: - rlm@10: -(defn no-matching-function-signature rlm@10: - {:dynamic true} rlm@10: - [function expected actual] rlm@10: - (report-problem function expected actual rlm@10: - "No matching real function signature for given argument count.")) rlm@10: - rlm@10: -(defn unexpected-args rlm@10: - {:dynamic true} rlm@10: - [function expected actual i] rlm@10: - (report-problem function expected actual rlm@10: - (str "Argument " i " has an unexpected value for function."))) rlm@10: - rlm@10: -(defn incorrect-invocation-count rlm@10: - {:dynamic true} rlm@10: - [function expected actual] rlm@10: - (report-problem function expected actual "Unexpected invocation count.")) rlm@10: - rlm@10: - rlm@10: -;;------------------------------------------------------------------------------ rlm@10: -;; Internal Functions - ignore these rlm@10: - rlm@10: - rlm@10: -(defn- has-arg-count-match? rlm@10: - "Given the sequence of accepted argument vectors for a function, rlm@10: -returns true if at least one matches the given-count value." rlm@10: - [arg-lists given-count] rlm@10: - (some #(let [[ind] (positions #{'&} %)] rlm@10: - (if ind rlm@10: - (>= given-count ind) rlm@10: - (= (count %) given-count))) rlm@10: - arg-lists)) rlm@10: - rlm@10: - rlm@10: -(defn has-matching-signature? rlm@10: - "Calls no-matching-function-signature if no match is found for the given rlm@10: -function. If no argslist meta data is available for the function, it is rlm@10: -not called." rlm@10: - [fn-name args] rlm@10: - (let [arg-count (count args) rlm@10: - arg-lists (:arglists (meta (resolve fn-name)))] rlm@10: - (if (and arg-lists (not (has-arg-count-match? arg-lists arg-count))) rlm@10: - (no-matching-function-signature fn-name arg-lists args)))) rlm@10: - rlm@10: - rlm@10: -(defn make-arg-checker rlm@10: - "Creates the argument verifying function for a replaced dependency within rlm@10: -the expectation bound scope. These functions take the additional argument rlm@10: -of the name of the replaced function, then the rest of their args. It is rlm@10: -designed to be called from the mock function generated in the first argument rlm@10: -of the mock info object created by make-mock." rlm@10: - [arg-preds arg-pred-forms] rlm@10: - (let [sanitized-preds (map (fn [v] (if (fn? v) v #(= v %))) arg-preds)] rlm@10: - (fn [fn-name & args] rlm@10: - (every? true? rlm@10: - (map (fn [pred arg pred-form i] (if (pred arg) true rlm@10: - (unexpected-args fn-name pred-form arg i))) rlm@10: - sanitized-preds args arg-pred-forms (iterate inc 0)))))) rlm@10: - rlm@10: - rlm@10: -(defn make-count-checker rlm@10: - "creates the count checker that is invoked at the end of an expectation, after rlm@10: -the code under test has all been executed. The function returned takes the rlm@10: -name of the associated dependency and the invocation count as arguments." rlm@10: - [pred pred-form] rlm@10: - (let [pred-fn (if (integer? pred) #(= pred %) pred)] rlm@10: - (fn [fn-name v] (if (pred-fn v) true rlm@10: - (incorrect-invocation-count fn-name pred-form v))))) rlm@10: - rlm@10: -; Borrowed from clojure core. Remove if this ever becomes public there. rlm@10: -(defmacro- assert-args rlm@10: - [fnname & pairs] rlm@10: - `(do (when-not ~(first pairs) rlm@10: - (throw (IllegalArgumentException. rlm@10: - ~(str fnname " requires " (second pairs))))) rlm@10: - ~(let [more (nnext pairs)] rlm@10: - (when more rlm@10: - (list* `assert-args fnname more))))) rlm@10: - rlm@10: -(defn make-mock rlm@10: - "creates a vector containing the following information for the named function: rlm@10: -1. dependent function replacement - verifies signature, calls arg checker, rlm@10: -increases count, returns return value. rlm@10: -2. an atom containing the invocation count rlm@10: -3. the invocation count checker function rlm@10: -4. a symbol of the name of the function being replaced." rlm@10: - [fn-name expectation-hash] rlm@10: - (assert-args make-mock rlm@10: - (map? expectation-hash) "a map of expectations") rlm@10: - (let [arg-checker (or (expectation-hash :has-args) (fn [& args] true)) rlm@10: - count-atom (atom 0) rlm@10: - ret-fn (or rlm@10: - (expectation-hash :calls) rlm@10: - (fn [& args] (expectation-hash :returns)))] rlm@10: - [(fn [& args] rlm@10: - (has-matching-signature? fn-name args) rlm@10: - (apply arg-checker fn-name args) rlm@10: - (swap! count-atom inc) rlm@10: - (apply ret-fn args)) rlm@10: - count-atom rlm@10: - (or (expectation-hash :times) (fn [fn-name v] true)) rlm@10: - fn-name])) rlm@10: - rlm@10: - rlm@10: -(defn validate-counts rlm@10: - "given the sequence of all mock data for the expectation, simply calls the rlm@10: -count checker for each dependency." rlm@10: - [mock-data] (doseq [[mfn i checker fn-name] mock-data] (checker fn-name @i))) rlm@10: - rlm@10: -(defn ^{:private true} make-bindings [expect-bindings mock-data-sym] rlm@10: - `[~@(interleave (map #(first %) (partition 2 expect-bindings)) rlm@10: - (map (fn [i] `(nth (nth ~mock-data-sym ~i) 0)) rlm@10: - (range (quot (count expect-bindings) 2))))]) rlm@10: - rlm@10: - rlm@10: -;;------------------------------------------------------------------------------ rlm@10: -;; These are convenience functions to improve the readability and use of this rlm@10: -;; library. Useful in expressions such as: rlm@10: -;; (expect [dep-fn1 (times (more-than 1) (returns 15)) etc) rlm@10: - rlm@10: -(defn once [x] (= 1 x)) rlm@10: - rlm@10: -(defn never [x] (zero? x)) rlm@10: - rlm@10: -(defn more-than [x] #(< x %)) rlm@10: - rlm@10: -(defn less-than [x] #(> x %)) rlm@10: - rlm@10: -(defn between [x y] #(and (< x %) (> y %))) rlm@10: - rlm@10: - rlm@10: -;;------------------------------------------------------------------------------ rlm@10: -;; The following functions can be used to build up the expectation hash. rlm@10: - rlm@10: -(defn returns rlm@10: - "Creates or associates to an existing expectation hash the :returns key with rlm@10: -a value to be returned by the expectation after a successful invocation rlm@10: -matching its expected arguments (if applicable). rlm@10: -Usage: rlm@10: -(returns ret-value expectation-hash?)" rlm@10: - rlm@10: - ([val] (returns val {})) rlm@10: - ([val expectation-hash] (assoc expectation-hash :returns val))) rlm@10: - rlm@10: - rlm@10: -(defn calls rlm@10: - "Creates or associates to an existing expectation hash the :calls key with a rlm@10: -function that will be called with the given arguments. The return value from rlm@10: -this function will be returned returned by the expected function. If both this rlm@10: -and returns are specified, the return value of \"calls\" will have precedence. rlm@10: -Usage: rlm@10: -(calls some-fn expectation-hash?)" rlm@10: - rlm@10: - ([val] (calls val {})) rlm@10: - ([val expectation-hash] (assoc expectation-hash :calls val))) rlm@10: - rlm@10: - rlm@10: -(defmacro has-args rlm@10: - "Creates or associates to an existing expectation hash the :has-args key with rlm@10: -a value corresponding to a function that will either return true if its rlm@10: -argument expectations are met or throw an exception with the details of the rlm@10: -first failed argument it encounters. rlm@10: -Only specify as many predicates as you are interested in verifying. The rest rlm@10: -of the values are safely ignored. rlm@10: -Usage: rlm@10: -(has-args [arg-pred-1 arg-pred-2 ... arg-pred-n] expectation-hash?)" rlm@10: - rlm@10: - ([arg-pred-forms] `(has-args ~arg-pred-forms {})) rlm@10: - ([arg-pred-forms expect-hash-form] rlm@10: - (assert-args has-args rlm@10: - (vector? arg-pred-forms) "a vector of argument predicates") rlm@10: - `(assoc ~expect-hash-form :has-args rlm@10: - (make-arg-checker ~arg-pred-forms '~arg-pred-forms)))) rlm@10: - rlm@10: - rlm@10: -(defmacro times rlm@10: - "Creates or associates to an existing expectation hash the :times key with a rlm@10: -value corresponding to a predicate function which expects an integer value. rlm@10: -This function can either be specified as the first argument to times or can be rlm@10: -the result of calling times with an integer argument, in which case the rlm@10: -predicate will default to being an exact match. This predicate is called at rlm@10: -the end of an expect expression to validate that an expected dependency rlm@10: -function was called the expected number of times. rlm@10: -Usage: rlm@10: -(times n) rlm@10: -(times #(> n %)) rlm@10: -(times n expectation-hash)" rlm@10: - ([times-fn] `(times ~times-fn {})) rlm@10: - ([times-fn expectation-hash] rlm@10: - `(assoc ~expectation-hash :times (make-count-checker ~times-fn '~times-fn)))) rlm@10: - rlm@10: - rlm@10: -;------------------------------------------------------------------------------- rlm@10: -; The main expect macro. rlm@10: -(defmacro expect rlm@10: - "Use expect to redirect calls to dependent functions that are made within the rlm@10: -code under test. Instead of calling the functions that would normally be used, rlm@10: -temporary stubs are used, which can verify function parameters and call counts. rlm@10: -Return values can also be specified as needed. rlm@10: -Usage: rlm@10: -(expect [dep-fn (has-args [arg-pred1] (times n (returns x)))] rlm@10: - (function-under-test a b c))" rlm@10: - rlm@10: - [expect-bindings & body] rlm@10: - (assert-args expect rlm@10: - (vector? expect-bindings) "a vector of expectation bindings" rlm@10: - (even? (count expect-bindings)) rlm@10: - "an even number of forms in expectation bindings") rlm@10: - (let [mock-data (gensym "mock-data_")] rlm@10: - `(let [~mock-data (map (fn [args#] rlm@10: - (apply clojure.contrib.mock/make-mock args#)) rlm@10: - ~(cons 'list (map (fn [[n m]] (vector (list 'quote n) m)) rlm@10: - (partition 2 expect-bindings))))] rlm@10: - (binding ~(make-bindings expect-bindings mock-data) ~@body) rlm@10: - (clojure.contrib.mock/validate-counts ~mock-data) true))) rlm@10: +;;; clojure.contrib.mock.clj: mocking/expectation framework for Clojure rlm@10: + rlm@10: +;; by Matt Clark rlm@10: + rlm@10: +;; Copyright (c) Matt Clark, 2009. All rights reserved. The use and rlm@10: +;; distribution terms for this software are covered by the Eclipse Public rlm@10: +;; License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can rlm@10: +;; be found in the file epl-v10.html at the root of this distribution. By rlm@10: +;; using this software in any fashion, you are agreeing to be bound by the rlm@10: +;; terms of this license. You must not remove this notice, or any other rlm@10: +;; from this software. rlm@10: +;;------------------------------------------------------------------------------ rlm@10: + rlm@10: +(comment rlm@10: + ;; Mock is a function mocking utility loosely based on various ruby and java rlm@10: + ;; mocking frameworks such as mockito, easymock, and rspec yet adapted to rlm@10: + ;; fit the functional style of clojure. rlm@10: + ;; rlm@10: + ;; Mock uses bindings to wrap the functions that are being tested and rlm@10: + ;; then validates the invocation count at the end. The expect macro is the rlm@10: + ;; main entry point and it is given a vector of binding pairs. rlm@10: + ;; The first of each pair names the dependent function you want to override rlm@10: + ;; while the second is a hashmap containing the mock description, usually rlm@10: + ;; created via the simple helper methods described below. rlm@10: + ;; rlm@10: + ;; Usage: rlm@10: + ;; rlm@10: + ;; there are one or more dependent functions: rlm@10: + rlm@10: + (defn dep-fn1 [] "time consuming calculation in 3rd party library") rlm@10: + (defn dep-fn2 [x] "function with undesirable side effects while testing") rlm@10: + rlm@10: + ;; then we have the code under test that calls these other functions: rlm@10: + rlm@10: + (defn my-code-under-test [] (dep-fn1) (dep-fn2 "a") (+ 2 2)) rlm@10: + rlm@10: + ;; to test this code, we simply surround it with an expect macro within rlm@10: + ;; the test: rlm@10: + rlm@10: + (expect [dep-fn1 (times 1) rlm@10: + dep-fn2 (times 1 (has-args [#(= "a" %)]))] rlm@10: + (my-code-under-test)) rlm@10: + rlm@10: + ;; When an expectation fails during execution of the function under test rlm@10: + ;; an error condition function is called with the name of the function rlm@10: + ;; being mocked, the expected form and the actual value. These rlm@10: + ;; error functions can be overridden to allow easy integration into rlm@10: + ;; test frameworks such as test-is by reporting errors in the function rlm@10: + ;; overrides. rlm@10: + rlm@10: + ) ;; end comment rlm@10: + rlm@10: +(ns clojure.contrib.mock rlm@10: + ^{:author "Matt Clark" rlm@10: + :doc "function mocking/expectations for Clojure" } rlm@10: + (:use [clojure.contrib.seq :only (positions)] rlm@10: + [clojure.contrib.def :only (defmacro-)])) rlm@10: + rlm@10: + rlm@10: +;;------------------------------------------------------------------------------ rlm@10: +;; These are the error condition functions. Override them to integrate into rlm@10: +;; the test framework of your choice, or to simply customize error handling. rlm@10: + rlm@10: +(defn report-problem rlm@10: + {:dynamic true} rlm@10: + ([function expected actual] rlm@10: + (report-problem function expected actual "Expectation not met.")) rlm@10: + ([function expected actual message] rlm@10: + (prn (str message " Function name: " function rlm@10: + " expected: " expected " actual: " actual)))) rlm@10: + rlm@10: +(defn no-matching-function-signature rlm@10: + {:dynamic true} rlm@10: + [function expected actual] rlm@10: + (report-problem function expected actual rlm@10: + "No matching real function signature for given argument count.")) rlm@10: + rlm@10: +(defn unexpected-args rlm@10: + {:dynamic true} rlm@10: + [function expected actual i] rlm@10: + (report-problem function expected actual rlm@10: + (str "Argument " i " has an unexpected value for function."))) rlm@10: + rlm@10: +(defn incorrect-invocation-count rlm@10: + {:dynamic true} rlm@10: + [function expected actual] rlm@10: + (report-problem function expected actual "Unexpected invocation count.")) rlm@10: + rlm@10: + rlm@10: +;;------------------------------------------------------------------------------ rlm@10: +;; Internal Functions - ignore these rlm@10: + rlm@10: + rlm@10: +(defn- has-arg-count-match? rlm@10: + "Given the sequence of accepted argument vectors for a function rlm@10: +returns true if at least one matches the given-count value." rlm@10: + [arg-lists given-count] rlm@10: + (some #(let [[ind] (positions #{'&} %)] rlm@10: + (if ind rlm@10: + (>= given-count ind) rlm@10: + (= (count %) given-count))) rlm@10: + arg-lists)) rlm@10: + rlm@10: + rlm@10: +(defn has-matching-signature? rlm@10: + "Calls no-matching-function-signature if no match is found for the given rlm@10: +function. If no argslist meta data is available for the function, it is rlm@10: +not called." rlm@10: + [fn-name args] rlm@10: + (let [arg-count (count args) rlm@10: + arg-lists (:arglists (meta (resolve fn-name)))] rlm@10: + (if (and arg-lists (not (has-arg-count-match? arg-lists arg-count))) rlm@10: + (no-matching-function-signature fn-name arg-lists args)))) rlm@10: + rlm@10: + rlm@10: +(defn make-arg-checker rlm@10: + "Creates the argument verifying function for a replaced dependency within rlm@10: +the expectation bound scope. These functions take the additional argument rlm@10: +of the name of the replaced function, then the rest of their args. It is rlm@10: +designed to be called from the mock function generated in the first argument rlm@10: +of the mock info object created by make-mock." rlm@10: + [arg-preds arg-pred-forms] rlm@10: + (let [sanitized-preds (map (fn [v] (if (fn? v) v #(= v %))) arg-preds)] rlm@10: + (fn [fn-name & args] rlm@10: + (every? true? rlm@10: + (map (fn [pred arg pred-form i] (if (pred arg) true rlm@10: + (unexpected-args fn-name rlm@10: + pred-form arg i))) rlm@10: + sanitized-preds args arg-pred-forms (iterate inc 0)))))) rlm@10: + rlm@10: + rlm@10: +(defn make-count-checker rlm@10: + "creates the count checker that is invoked at the end of an expectation, after rlm@10: +the code under test has all been executed. The function returned takes the rlm@10: +name of the associated dependency and the invocation count as arguments." rlm@10: + [pred pred-form] rlm@10: + (let [pred-fn (if (integer? pred) #(= pred %) pred)] rlm@10: + (fn [fn-name v] (if (pred-fn v) true rlm@10: + (incorrect-invocation-count fn-name pred-form v))))) rlm@10: + rlm@10: +(defn make-mock rlm@10: + "creates a vector containing the following information for the named function: rlm@10: +1. dependent function replacement - verifies signature, calls arg checker rlm@10: +increases count, returns return value. rlm@10: +2. an atom containing the invocation count rlm@10: +3. the invocation count checker function rlm@10: +4. a symbol of the name of the function being replaced." rlm@10: + [fn-name expectation-hash] rlm@10: + {:pre [(map? expectation-hash) rlm@10: + (symbol? fn-name)]} rlm@10: + (let [arg-checker (or (expectation-hash :has-args) (fn [& args] true)) rlm@10: + count-atom (atom 0) rlm@10: + ret-fn (or rlm@10: + (expectation-hash :calls) rlm@10: + (fn [& args] (expectation-hash :returns)))] rlm@10: + [(fn [& args] rlm@10: + (has-matching-signature? fn-name args) rlm@10: + (apply arg-checker fn-name args) rlm@10: + (swap! count-atom inc) rlm@10: + (apply ret-fn args)) rlm@10: + count-atom rlm@10: + (or (expectation-hash :times) (fn [fn-name v] true)) rlm@10: + fn-name])) rlm@10: + rlm@10: + rlm@10: +(defn validate-counts rlm@10: + "given the sequence of all mock data for the expectation, simply calls the rlm@10: +count checker for each dependency." rlm@10: + [mock-data] (doseq [[mfn i checker fn-name] mock-data] (checker fn-name @i))) rlm@10: + rlm@10: +(defn- make-bindings [expect-bindings mock-data-sym] rlm@10: + `[~@(interleave (map #(first %) (partition 2 expect-bindings)) rlm@10: + (map (fn [i] `(nth (nth ~mock-data-sym ~i) 0)) rlm@10: + (range (quot (count expect-bindings) 2))))]) rlm@10: + rlm@10: + rlm@10: +;;------------------------------------------------------------------------------ rlm@10: +;; These are convenience functions to improve the readability and use of this rlm@10: +;; library. Useful in expressions such as: rlm@10: +;; (expect [dep-fn1 (times (more-than 1) (returns 15)) etc) rlm@10: + rlm@10: +;; best used in the times function rlm@10: +(defn once [x] (= 1 x)) rlm@10: + rlm@10: +(defn never [x] (zero? x)) rlm@10: + rlm@10: +(defn more-than [x] #(< x %)) rlm@10: + rlm@10: +(defn less-than [x] #(> x %)) rlm@10: + rlm@10: +(defn between [x y] #(and (< x %) (> y %))) rlm@10: + rlm@10: +;;best used in the has-args function rlm@10: +(defn anything [x] true) rlm@10: + rlm@10: + rlm@10: +;;------------------------------------------------------------------------------ rlm@10: +;; The following functions can be used to build up the expectation hash. rlm@10: + rlm@10: +(defn returns rlm@10: + "Creates or associates to an existing expectation hash the :returns key with rlm@10: +a value to be returned by the expectation after a successful invocation rlm@10: +matching its expected arguments (if applicable). rlm@10: +Usage: rlm@10: +(returns ret-value expectation-hash?)" rlm@10: + rlm@10: + ([val] (returns val {})) rlm@10: + ([val expectation-hash] rlm@10: + {:pre [(map? expectation-hash)]} rlm@10: + (assoc expectation-hash :returns val))) rlm@10: + rlm@10: + rlm@10: +(defn calls rlm@10: + "Creates or associates to an existing expectation hash the :calls key with a rlm@10: +function that will be called with the given arguments. The return value from rlm@10: +this function will be returned by the expected function. If both this rlm@10: +and returns are specified, the return value of \"calls\" will have precedence. rlm@10: +Usage: rlm@10: +(calls some-fn expectation-hash?)" rlm@10: + rlm@10: + ([val] (calls val {})) rlm@10: + ([val expectation-hash] rlm@10: + {:pre [(map? expectation-hash)]} rlm@10: + (assoc expectation-hash :calls val))) rlm@10: + rlm@10: + rlm@10: +(defmacro has-args rlm@10: + "Creates or associates to an existing expectation hash the :has-args key with rlm@10: +a value corresponding to a function that will either return true if its rlm@10: +argument expectations are met or throw an exception with the details of the rlm@10: +first failed argument it encounters. rlm@10: +Only specify as many predicates as you are interested in verifying. The rest rlm@10: +of the values are safely ignored. rlm@10: +Usage: rlm@10: +(has-args [arg-pred-1 arg-pred-2 ... arg-pred-n] expectation-hash?)" rlm@10: + rlm@10: + ([arg-pred-forms] `(has-args ~arg-pred-forms {})) rlm@10: + ([arg-pred-forms expectation-hash] rlm@10: + {:pre [(vector? arg-pred-forms) rlm@10: + (map? expectation-hash)]} rlm@10: + `(assoc ~expectation-hash :has-args rlm@10: + (make-arg-checker ~arg-pred-forms '~arg-pred-forms)))) rlm@10: + rlm@10: + rlm@10: +(defmacro times rlm@10: + "Creates or associates to an existing expectation hash the :times key with a rlm@10: +value corresponding to a predicate function which expects an integer value. rlm@10: +Also, an integer can be specified, in which case the times will only be an rlm@10: +exact match. The times check is called at the end of an expect expression to rlm@10: +validate that an expected dependency function was called the expected rlm@10: +number of times. rlm@10: +Usage: rlm@10: +(times n) rlm@10: +(times #(> n %)) rlm@10: +(times n expectation-hash)" rlm@10: + ([times-fn] `(times ~times-fn {})) rlm@10: + ([times-fn expectation-hash] rlm@10: + {:pre [(map? expectation-hash)]} rlm@10: + `(assoc ~expectation-hash :times (make-count-checker ~times-fn '~times-fn)))) rlm@10: + rlm@10: + rlm@10: +;------------------------------------------------------------------------------- rlm@10: +; The main expect macro. rlm@10: +(defmacro expect rlm@10: + "Use expect to redirect calls to dependent functions that are made within the rlm@10: +code under test. Instead of calling the functions that would normally be used rlm@10: +temporary stubs are used, which can verify function parameters and call counts. rlm@10: +Return values of overridden functions can also be specified as needed. rlm@10: +Usage: rlm@10: +(expect [dep-fn (has-args [arg-pred1] (times n (returns x)))] rlm@10: + (function-under-test a b c))" rlm@10: + rlm@10: + [expect-bindings & body] rlm@10: + {:pre [(vector? expect-bindings) rlm@10: + (even? (count expect-bindings))]} rlm@10: + (let [mock-data (gensym "mock-data_")] rlm@10: + `(let [~mock-data (map (fn [args#] rlm@10: + (apply clojure.contrib.mock/make-mock args#)) rlm@10: + ~(cons 'list (map (fn [[n m]] (vector (list 'quote n) m)) rlm@10: + (partition 2 expect-bindings))))] rlm@10: + (binding ~(make-bindings expect-bindings mock-data) ~@body) rlm@10: + (clojure.contrib.mock/validate-counts ~mock-data) true)))