rlm@10
|
1 diff a/src/main/clojure/clojure/contrib/mock.clj b/src/main/clojure/clojure/contrib/mock.clj (rejected hunks)
|
rlm@10
|
2 @@ -1,285 +1,282 @@
|
rlm@10
|
3 -;;; clojure.contrib.mock.clj: mocking/expectation framework for Clojure
|
rlm@10
|
4 -
|
rlm@10
|
5 -;; by Matt Clark
|
rlm@10
|
6 -
|
rlm@10
|
7 -;; Copyright (c) Matt Clark, 2009. All rights reserved. The use
|
rlm@10
|
8 -;; and distribution terms for this software are covered by the Eclipse
|
rlm@10
|
9 -;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php).
|
rlm@10
|
10 -;; By using this software in any fashion, you are
|
rlm@10
|
11 -;; agreeing to be bound by the terms of this license. You must not
|
rlm@10
|
12 -;; remove this notice, or any other, from this software.
|
rlm@10
|
13 -;;------------------------------------------------------------------------------
|
rlm@10
|
14 -
|
rlm@10
|
15 -(comment
|
rlm@10
|
16 - ;; This is a simple function mocking library I accidentally wrote as a side
|
rlm@10
|
17 - ;; effect of trying to write an opengl library in clojure. This is loosely
|
rlm@10
|
18 - ;; based on various ruby and java mocking frameworks I have used in the past
|
rlm@10
|
19 - ;; such as mockito, easymock, and whatever rspec uses.
|
rlm@10
|
20 - ;;
|
rlm@10
|
21 - ;; expect uses bindings to wrap the functions that are being tested and
|
rlm@10
|
22 - ;; then validates the invocation count at the end. The expect macro is the
|
rlm@10
|
23 - ;; main entry point and it is given a vector of binding pairs.
|
rlm@10
|
24 - ;; The first of each pair names the dependent function you want to override,
|
rlm@10
|
25 - ;; while the second is a hashmap containing the mock description, usually
|
rlm@10
|
26 - ;; created via the simple helper methods described below.
|
rlm@10
|
27 - ;;
|
rlm@10
|
28 - ;; Usage:
|
rlm@10
|
29 - ;;
|
rlm@10
|
30 - ;; there are one or more dependent functions:
|
rlm@10
|
31 -
|
rlm@10
|
32 - (defn dep-fn1 [] "time consuming calculation in 3rd party library")
|
rlm@10
|
33 - (defn dep-fn2 [x] "function with undesirable side effects while testing")
|
rlm@10
|
34 -
|
rlm@10
|
35 - ;; then we have the code under test that calls these other functions:
|
rlm@10
|
36 -
|
rlm@10
|
37 - (defn my-code-under-test [] (dep-fn1) (dep-fn2 "a") (+ 2 2))
|
rlm@10
|
38 -
|
rlm@10
|
39 - ;; to test this code, we simply surround it with an expect macro within
|
rlm@10
|
40 - ;; the test:
|
rlm@10
|
41 -
|
rlm@10
|
42 - (expect [dep-fn1 (times 1)
|
rlm@10
|
43 - dep-fn2 (times 1 (has-args [#(= "a" %)]))]
|
rlm@10
|
44 - (my-code-under-test))
|
rlm@10
|
45 -
|
rlm@10
|
46 - ;; When an expectation fails during execution of the function under test,
|
rlm@10
|
47 - ;; an error condition function is called with the name of the function
|
rlm@10
|
48 - ;; being mocked, the expected form and the actual value. These
|
rlm@10
|
49 - ;; error functions can be overridden to allow easy integration into
|
rlm@10
|
50 - ;; test frameworks such as test-is by reporting errors in the function
|
rlm@10
|
51 - ;; overrides.
|
rlm@10
|
52 -
|
rlm@10
|
53 - ) ;; end comment
|
rlm@10
|
54 -
|
rlm@10
|
55 -(ns clojure.contrib.mock
|
rlm@10
|
56 - ^{:author "Matt Clark",
|
rlm@10
|
57 - :doc "function mocking/expectations for Clojure" }
|
rlm@10
|
58 - (:use [clojure.contrib.seq :only (positions)]
|
rlm@10
|
59 - [clojure.contrib.def :only (defmacro-)]))
|
rlm@10
|
60 -
|
rlm@10
|
61 -
|
rlm@10
|
62 -;;------------------------------------------------------------------------------
|
rlm@10
|
63 -;; These are the error condition functions. Override them to integrate into
|
rlm@10
|
64 -;; the test framework of your choice, or to simply customize error handling.
|
rlm@10
|
65 -
|
rlm@10
|
66 -(defn report-problem
|
rlm@10
|
67 - {:dynamic true}
|
rlm@10
|
68 - ([function expected actual]
|
rlm@10
|
69 - (report-problem function expected actual "Expectation not met."))
|
rlm@10
|
70 - ([function expected actual message]
|
rlm@10
|
71 - (prn (str message " Function name: " function
|
rlm@10
|
72 - " expected: " expected " actual: " actual))))
|
rlm@10
|
73 -
|
rlm@10
|
74 -(defn no-matching-function-signature
|
rlm@10
|
75 - {:dynamic true}
|
rlm@10
|
76 - [function expected actual]
|
rlm@10
|
77 - (report-problem function expected actual
|
rlm@10
|
78 - "No matching real function signature for given argument count."))
|
rlm@10
|
79 -
|
rlm@10
|
80 -(defn unexpected-args
|
rlm@10
|
81 - {:dynamic true}
|
rlm@10
|
82 - [function expected actual i]
|
rlm@10
|
83 - (report-problem function expected actual
|
rlm@10
|
84 - (str "Argument " i " has an unexpected value for function.")))
|
rlm@10
|
85 -
|
rlm@10
|
86 -(defn incorrect-invocation-count
|
rlm@10
|
87 - {:dynamic true}
|
rlm@10
|
88 - [function expected actual]
|
rlm@10
|
89 - (report-problem function expected actual "Unexpected invocation count."))
|
rlm@10
|
90 -
|
rlm@10
|
91 -
|
rlm@10
|
92 -;;------------------------------------------------------------------------------
|
rlm@10
|
93 -;; Internal Functions - ignore these
|
rlm@10
|
94 -
|
rlm@10
|
95 -
|
rlm@10
|
96 -(defn- has-arg-count-match?
|
rlm@10
|
97 - "Given the sequence of accepted argument vectors for a function,
|
rlm@10
|
98 -returns true if at least one matches the given-count value."
|
rlm@10
|
99 - [arg-lists given-count]
|
rlm@10
|
100 - (some #(let [[ind] (positions #{'&} %)]
|
rlm@10
|
101 - (if ind
|
rlm@10
|
102 - (>= given-count ind)
|
rlm@10
|
103 - (= (count %) given-count)))
|
rlm@10
|
104 - arg-lists))
|
rlm@10
|
105 -
|
rlm@10
|
106 -
|
rlm@10
|
107 -(defn has-matching-signature?
|
rlm@10
|
108 - "Calls no-matching-function-signature if no match is found for the given
|
rlm@10
|
109 -function. If no argslist meta data is available for the function, it is
|
rlm@10
|
110 -not called."
|
rlm@10
|
111 - [fn-name args]
|
rlm@10
|
112 - (let [arg-count (count args)
|
rlm@10
|
113 - arg-lists (:arglists (meta (resolve fn-name)))]
|
rlm@10
|
114 - (if (and arg-lists (not (has-arg-count-match? arg-lists arg-count)))
|
rlm@10
|
115 - (no-matching-function-signature fn-name arg-lists args))))
|
rlm@10
|
116 -
|
rlm@10
|
117 -
|
rlm@10
|
118 -(defn make-arg-checker
|
rlm@10
|
119 - "Creates the argument verifying function for a replaced dependency within
|
rlm@10
|
120 -the expectation bound scope. These functions take the additional argument
|
rlm@10
|
121 -of the name of the replaced function, then the rest of their args. It is
|
rlm@10
|
122 -designed to be called from the mock function generated in the first argument
|
rlm@10
|
123 -of the mock info object created by make-mock."
|
rlm@10
|
124 - [arg-preds arg-pred-forms]
|
rlm@10
|
125 - (let [sanitized-preds (map (fn [v] (if (fn? v) v #(= v %))) arg-preds)]
|
rlm@10
|
126 - (fn [fn-name & args]
|
rlm@10
|
127 - (every? true?
|
rlm@10
|
128 - (map (fn [pred arg pred-form i] (if (pred arg) true
|
rlm@10
|
129 - (unexpected-args fn-name pred-form arg i)))
|
rlm@10
|
130 - sanitized-preds args arg-pred-forms (iterate inc 0))))))
|
rlm@10
|
131 -
|
rlm@10
|
132 -
|
rlm@10
|
133 -(defn make-count-checker
|
rlm@10
|
134 - "creates the count checker that is invoked at the end of an expectation, after
|
rlm@10
|
135 -the code under test has all been executed. The function returned takes the
|
rlm@10
|
136 -name of the associated dependency and the invocation count as arguments."
|
rlm@10
|
137 - [pred pred-form]
|
rlm@10
|
138 - (let [pred-fn (if (integer? pred) #(= pred %) pred)]
|
rlm@10
|
139 - (fn [fn-name v] (if (pred-fn v) true
|
rlm@10
|
140 - (incorrect-invocation-count fn-name pred-form v)))))
|
rlm@10
|
141 -
|
rlm@10
|
142 -; Borrowed from clojure core. Remove if this ever becomes public there.
|
rlm@10
|
143 -(defmacro- assert-args
|
rlm@10
|
144 - [fnname & pairs]
|
rlm@10
|
145 - `(do (when-not ~(first pairs)
|
rlm@10
|
146 - (throw (IllegalArgumentException.
|
rlm@10
|
147 - ~(str fnname " requires " (second pairs)))))
|
rlm@10
|
148 - ~(let [more (nnext pairs)]
|
rlm@10
|
149 - (when more
|
rlm@10
|
150 - (list* `assert-args fnname more)))))
|
rlm@10
|
151 -
|
rlm@10
|
152 -(defn make-mock
|
rlm@10
|
153 - "creates a vector containing the following information for the named function:
|
rlm@10
|
154 -1. dependent function replacement - verifies signature, calls arg checker,
|
rlm@10
|
155 -increases count, returns return value.
|
rlm@10
|
156 -2. an atom containing the invocation count
|
rlm@10
|
157 -3. the invocation count checker function
|
rlm@10
|
158 -4. a symbol of the name of the function being replaced."
|
rlm@10
|
159 - [fn-name expectation-hash]
|
rlm@10
|
160 - (assert-args make-mock
|
rlm@10
|
161 - (map? expectation-hash) "a map of expectations")
|
rlm@10
|
162 - (let [arg-checker (or (expectation-hash :has-args) (fn [& args] true))
|
rlm@10
|
163 - count-atom (atom 0)
|
rlm@10
|
164 - ret-fn (or
|
rlm@10
|
165 - (expectation-hash :calls)
|
rlm@10
|
166 - (fn [& args] (expectation-hash :returns)))]
|
rlm@10
|
167 - [(fn [& args]
|
rlm@10
|
168 - (has-matching-signature? fn-name args)
|
rlm@10
|
169 - (apply arg-checker fn-name args)
|
rlm@10
|
170 - (swap! count-atom inc)
|
rlm@10
|
171 - (apply ret-fn args))
|
rlm@10
|
172 - count-atom
|
rlm@10
|
173 - (or (expectation-hash :times) (fn [fn-name v] true))
|
rlm@10
|
174 - fn-name]))
|
rlm@10
|
175 -
|
rlm@10
|
176 -
|
rlm@10
|
177 -(defn validate-counts
|
rlm@10
|
178 - "given the sequence of all mock data for the expectation, simply calls the
|
rlm@10
|
179 -count checker for each dependency."
|
rlm@10
|
180 - [mock-data] (doseq [[mfn i checker fn-name] mock-data] (checker fn-name @i)))
|
rlm@10
|
181 -
|
rlm@10
|
182 -(defn ^{:private true} make-bindings [expect-bindings mock-data-sym]
|
rlm@10
|
183 - `[~@(interleave (map #(first %) (partition 2 expect-bindings))
|
rlm@10
|
184 - (map (fn [i] `(nth (nth ~mock-data-sym ~i) 0))
|
rlm@10
|
185 - (range (quot (count expect-bindings) 2))))])
|
rlm@10
|
186 -
|
rlm@10
|
187 -
|
rlm@10
|
188 -;;------------------------------------------------------------------------------
|
rlm@10
|
189 -;; These are convenience functions to improve the readability and use of this
|
rlm@10
|
190 -;; library. Useful in expressions such as:
|
rlm@10
|
191 -;; (expect [dep-fn1 (times (more-than 1) (returns 15)) etc)
|
rlm@10
|
192 -
|
rlm@10
|
193 -(defn once [x] (= 1 x))
|
rlm@10
|
194 -
|
rlm@10
|
195 -(defn never [x] (zero? x))
|
rlm@10
|
196 -
|
rlm@10
|
197 -(defn more-than [x] #(< x %))
|
rlm@10
|
198 -
|
rlm@10
|
199 -(defn less-than [x] #(> x %))
|
rlm@10
|
200 -
|
rlm@10
|
201 -(defn between [x y] #(and (< x %) (> y %)))
|
rlm@10
|
202 -
|
rlm@10
|
203 -
|
rlm@10
|
204 -;;------------------------------------------------------------------------------
|
rlm@10
|
205 -;; The following functions can be used to build up the expectation hash.
|
rlm@10
|
206 -
|
rlm@10
|
207 -(defn returns
|
rlm@10
|
208 - "Creates or associates to an existing expectation hash the :returns key with
|
rlm@10
|
209 -a value to be returned by the expectation after a successful invocation
|
rlm@10
|
210 -matching its expected arguments (if applicable).
|
rlm@10
|
211 -Usage:
|
rlm@10
|
212 -(returns ret-value expectation-hash?)"
|
rlm@10
|
213 -
|
rlm@10
|
214 - ([val] (returns val {}))
|
rlm@10
|
215 - ([val expectation-hash] (assoc expectation-hash :returns val)))
|
rlm@10
|
216 -
|
rlm@10
|
217 -
|
rlm@10
|
218 -(defn calls
|
rlm@10
|
219 - "Creates or associates to an existing expectation hash the :calls key with a
|
rlm@10
|
220 -function that will be called with the given arguments. The return value from
|
rlm@10
|
221 -this function will be returned returned by the expected function. If both this
|
rlm@10
|
222 -and returns are specified, the return value of \"calls\" will have precedence.
|
rlm@10
|
223 -Usage:
|
rlm@10
|
224 -(calls some-fn expectation-hash?)"
|
rlm@10
|
225 -
|
rlm@10
|
226 - ([val] (calls val {}))
|
rlm@10
|
227 - ([val expectation-hash] (assoc expectation-hash :calls val)))
|
rlm@10
|
228 -
|
rlm@10
|
229 -
|
rlm@10
|
230 -(defmacro has-args
|
rlm@10
|
231 - "Creates or associates to an existing expectation hash the :has-args key with
|
rlm@10
|
232 -a value corresponding to a function that will either return true if its
|
rlm@10
|
233 -argument expectations are met or throw an exception with the details of the
|
rlm@10
|
234 -first failed argument it encounters.
|
rlm@10
|
235 -Only specify as many predicates as you are interested in verifying. The rest
|
rlm@10
|
236 -of the values are safely ignored.
|
rlm@10
|
237 -Usage:
|
rlm@10
|
238 -(has-args [arg-pred-1 arg-pred-2 ... arg-pred-n] expectation-hash?)"
|
rlm@10
|
239 -
|
rlm@10
|
240 - ([arg-pred-forms] `(has-args ~arg-pred-forms {}))
|
rlm@10
|
241 - ([arg-pred-forms expect-hash-form]
|
rlm@10
|
242 - (assert-args has-args
|
rlm@10
|
243 - (vector? arg-pred-forms) "a vector of argument predicates")
|
rlm@10
|
244 - `(assoc ~expect-hash-form :has-args
|
rlm@10
|
245 - (make-arg-checker ~arg-pred-forms '~arg-pred-forms))))
|
rlm@10
|
246 -
|
rlm@10
|
247 -
|
rlm@10
|
248 -(defmacro times
|
rlm@10
|
249 - "Creates or associates to an existing expectation hash the :times key with a
|
rlm@10
|
250 -value corresponding to a predicate function which expects an integer value.
|
rlm@10
|
251 -This function can either be specified as the first argument to times or can be
|
rlm@10
|
252 -the result of calling times with an integer argument, in which case the
|
rlm@10
|
253 -predicate will default to being an exact match. This predicate is called at
|
rlm@10
|
254 -the end of an expect expression to validate that an expected dependency
|
rlm@10
|
255 -function was called the expected number of times.
|
rlm@10
|
256 -Usage:
|
rlm@10
|
257 -(times n)
|
rlm@10
|
258 -(times #(> n %))
|
rlm@10
|
259 -(times n expectation-hash)"
|
rlm@10
|
260 - ([times-fn] `(times ~times-fn {}))
|
rlm@10
|
261 - ([times-fn expectation-hash]
|
rlm@10
|
262 - `(assoc ~expectation-hash :times (make-count-checker ~times-fn '~times-fn))))
|
rlm@10
|
263 -
|
rlm@10
|
264 -
|
rlm@10
|
265 -;-------------------------------------------------------------------------------
|
rlm@10
|
266 -; The main expect macro.
|
rlm@10
|
267 -(defmacro expect
|
rlm@10
|
268 - "Use expect to redirect calls to dependent functions that are made within the
|
rlm@10
|
269 -code under test. Instead of calling the functions that would normally be used,
|
rlm@10
|
270 -temporary stubs are used, which can verify function parameters and call counts.
|
rlm@10
|
271 -Return values can also be specified as needed.
|
rlm@10
|
272 -Usage:
|
rlm@10
|
273 -(expect [dep-fn (has-args [arg-pred1] (times n (returns x)))]
|
rlm@10
|
274 - (function-under-test a b c))"
|
rlm@10
|
275 -
|
rlm@10
|
276 - [expect-bindings & body]
|
rlm@10
|
277 - (assert-args expect
|
rlm@10
|
278 - (vector? expect-bindings) "a vector of expectation bindings"
|
rlm@10
|
279 - (even? (count expect-bindings))
|
rlm@10
|
280 - "an even number of forms in expectation bindings")
|
rlm@10
|
281 - (let [mock-data (gensym "mock-data_")]
|
rlm@10
|
282 - `(let [~mock-data (map (fn [args#]
|
rlm@10
|
283 - (apply clojure.contrib.mock/make-mock args#))
|
rlm@10
|
284 - ~(cons 'list (map (fn [[n m]] (vector (list 'quote n) m))
|
rlm@10
|
285 - (partition 2 expect-bindings))))]
|
rlm@10
|
286 - (binding ~(make-bindings expect-bindings mock-data) ~@body)
|
rlm@10
|
287 - (clojure.contrib.mock/validate-counts ~mock-data) true)))
|
rlm@10
|
288 +;;; clojure.contrib.mock.clj: mocking/expectation framework for Clojure
|
rlm@10
|
289 +
|
rlm@10
|
290 +;; by Matt Clark
|
rlm@10
|
291 +
|
rlm@10
|
292 +;; Copyright (c) Matt Clark, 2009. All rights reserved. The use and
|
rlm@10
|
293 +;; distribution terms for this software are covered by the Eclipse Public
|
rlm@10
|
294 +;; License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can
|
rlm@10
|
295 +;; be found in the file epl-v10.html at the root of this distribution. By
|
rlm@10
|
296 +;; using this software in any fashion, you are agreeing to be bound by the
|
rlm@10
|
297 +;; terms of this license. You must not remove this notice, or any other
|
rlm@10
|
298 +;; from this software.
|
rlm@10
|
299 +;;------------------------------------------------------------------------------
|
rlm@10
|
300 +
|
rlm@10
|
301 +(comment
|
rlm@10
|
302 + ;; Mock is a function mocking utility loosely based on various ruby and java
|
rlm@10
|
303 + ;; mocking frameworks such as mockito, easymock, and rspec yet adapted to
|
rlm@10
|
304 + ;; fit the functional style of clojure.
|
rlm@10
|
305 + ;;
|
rlm@10
|
306 + ;; Mock uses bindings to wrap the functions that are being tested and
|
rlm@10
|
307 + ;; then validates the invocation count at the end. The expect macro is the
|
rlm@10
|
308 + ;; main entry point and it is given a vector of binding pairs.
|
rlm@10
|
309 + ;; The first of each pair names the dependent function you want to override
|
rlm@10
|
310 + ;; while the second is a hashmap containing the mock description, usually
|
rlm@10
|
311 + ;; created via the simple helper methods described below.
|
rlm@10
|
312 + ;;
|
rlm@10
|
313 + ;; Usage:
|
rlm@10
|
314 + ;;
|
rlm@10
|
315 + ;; there are one or more dependent functions:
|
rlm@10
|
316 +
|
rlm@10
|
317 + (defn dep-fn1 [] "time consuming calculation in 3rd party library")
|
rlm@10
|
318 + (defn dep-fn2 [x] "function with undesirable side effects while testing")
|
rlm@10
|
319 +
|
rlm@10
|
320 + ;; then we have the code under test that calls these other functions:
|
rlm@10
|
321 +
|
rlm@10
|
322 + (defn my-code-under-test [] (dep-fn1) (dep-fn2 "a") (+ 2 2))
|
rlm@10
|
323 +
|
rlm@10
|
324 + ;; to test this code, we simply surround it with an expect macro within
|
rlm@10
|
325 + ;; the test:
|
rlm@10
|
326 +
|
rlm@10
|
327 + (expect [dep-fn1 (times 1)
|
rlm@10
|
328 + dep-fn2 (times 1 (has-args [#(= "a" %)]))]
|
rlm@10
|
329 + (my-code-under-test))
|
rlm@10
|
330 +
|
rlm@10
|
331 + ;; When an expectation fails during execution of the function under test
|
rlm@10
|
332 + ;; an error condition function is called with the name of the function
|
rlm@10
|
333 + ;; being mocked, the expected form and the actual value. These
|
rlm@10
|
334 + ;; error functions can be overridden to allow easy integration into
|
rlm@10
|
335 + ;; test frameworks such as test-is by reporting errors in the function
|
rlm@10
|
336 + ;; overrides.
|
rlm@10
|
337 +
|
rlm@10
|
338 + ) ;; end comment
|
rlm@10
|
339 +
|
rlm@10
|
340 +(ns clojure.contrib.mock
|
rlm@10
|
341 + ^{:author "Matt Clark"
|
rlm@10
|
342 + :doc "function mocking/expectations for Clojure" }
|
rlm@10
|
343 + (:use [clojure.contrib.seq :only (positions)]
|
rlm@10
|
344 + [clojure.contrib.def :only (defmacro-)]))
|
rlm@10
|
345 +
|
rlm@10
|
346 +
|
rlm@10
|
347 +;;------------------------------------------------------------------------------
|
rlm@10
|
348 +;; These are the error condition functions. Override them to integrate into
|
rlm@10
|
349 +;; the test framework of your choice, or to simply customize error handling.
|
rlm@10
|
350 +
|
rlm@10
|
351 +(defn report-problem
|
rlm@10
|
352 + {:dynamic true}
|
rlm@10
|
353 + ([function expected actual]
|
rlm@10
|
354 + (report-problem function expected actual "Expectation not met."))
|
rlm@10
|
355 + ([function expected actual message]
|
rlm@10
|
356 + (prn (str message " Function name: " function
|
rlm@10
|
357 + " expected: " expected " actual: " actual))))
|
rlm@10
|
358 +
|
rlm@10
|
359 +(defn no-matching-function-signature
|
rlm@10
|
360 + {:dynamic true}
|
rlm@10
|
361 + [function expected actual]
|
rlm@10
|
362 + (report-problem function expected actual
|
rlm@10
|
363 + "No matching real function signature for given argument count."))
|
rlm@10
|
364 +
|
rlm@10
|
365 +(defn unexpected-args
|
rlm@10
|
366 + {:dynamic true}
|
rlm@10
|
367 + [function expected actual i]
|
rlm@10
|
368 + (report-problem function expected actual
|
rlm@10
|
369 + (str "Argument " i " has an unexpected value for function.")))
|
rlm@10
|
370 +
|
rlm@10
|
371 +(defn incorrect-invocation-count
|
rlm@10
|
372 + {:dynamic true}
|
rlm@10
|
373 + [function expected actual]
|
rlm@10
|
374 + (report-problem function expected actual "Unexpected invocation count."))
|
rlm@10
|
375 +
|
rlm@10
|
376 +
|
rlm@10
|
377 +;;------------------------------------------------------------------------------
|
rlm@10
|
378 +;; Internal Functions - ignore these
|
rlm@10
|
379 +
|
rlm@10
|
380 +
|
rlm@10
|
381 +(defn- has-arg-count-match?
|
rlm@10
|
382 + "Given the sequence of accepted argument vectors for a function
|
rlm@10
|
383 +returns true if at least one matches the given-count value."
|
rlm@10
|
384 + [arg-lists given-count]
|
rlm@10
|
385 + (some #(let [[ind] (positions #{'&} %)]
|
rlm@10
|
386 + (if ind
|
rlm@10
|
387 + (>= given-count ind)
|
rlm@10
|
388 + (= (count %) given-count)))
|
rlm@10
|
389 + arg-lists))
|
rlm@10
|
390 +
|
rlm@10
|
391 +
|
rlm@10
|
392 +(defn has-matching-signature?
|
rlm@10
|
393 + "Calls no-matching-function-signature if no match is found for the given
|
rlm@10
|
394 +function. If no argslist meta data is available for the function, it is
|
rlm@10
|
395 +not called."
|
rlm@10
|
396 + [fn-name args]
|
rlm@10
|
397 + (let [arg-count (count args)
|
rlm@10
|
398 + arg-lists (:arglists (meta (resolve fn-name)))]
|
rlm@10
|
399 + (if (and arg-lists (not (has-arg-count-match? arg-lists arg-count)))
|
rlm@10
|
400 + (no-matching-function-signature fn-name arg-lists args))))
|
rlm@10
|
401 +
|
rlm@10
|
402 +
|
rlm@10
|
403 +(defn make-arg-checker
|
rlm@10
|
404 + "Creates the argument verifying function for a replaced dependency within
|
rlm@10
|
405 +the expectation bound scope. These functions take the additional argument
|
rlm@10
|
406 +of the name of the replaced function, then the rest of their args. It is
|
rlm@10
|
407 +designed to be called from the mock function generated in the first argument
|
rlm@10
|
408 +of the mock info object created by make-mock."
|
rlm@10
|
409 + [arg-preds arg-pred-forms]
|
rlm@10
|
410 + (let [sanitized-preds (map (fn [v] (if (fn? v) v #(= v %))) arg-preds)]
|
rlm@10
|
411 + (fn [fn-name & args]
|
rlm@10
|
412 + (every? true?
|
rlm@10
|
413 + (map (fn [pred arg pred-form i] (if (pred arg) true
|
rlm@10
|
414 + (unexpected-args fn-name
|
rlm@10
|
415 + pred-form arg i)))
|
rlm@10
|
416 + sanitized-preds args arg-pred-forms (iterate inc 0))))))
|
rlm@10
|
417 +
|
rlm@10
|
418 +
|
rlm@10
|
419 +(defn make-count-checker
|
rlm@10
|
420 + "creates the count checker that is invoked at the end of an expectation, after
|
rlm@10
|
421 +the code under test has all been executed. The function returned takes the
|
rlm@10
|
422 +name of the associated dependency and the invocation count as arguments."
|
rlm@10
|
423 + [pred pred-form]
|
rlm@10
|
424 + (let [pred-fn (if (integer? pred) #(= pred %) pred)]
|
rlm@10
|
425 + (fn [fn-name v] (if (pred-fn v) true
|
rlm@10
|
426 + (incorrect-invocation-count fn-name pred-form v)))))
|
rlm@10
|
427 +
|
rlm@10
|
428 +(defn make-mock
|
rlm@10
|
429 + "creates a vector containing the following information for the named function:
|
rlm@10
|
430 +1. dependent function replacement - verifies signature, calls arg checker
|
rlm@10
|
431 +increases count, returns return value.
|
rlm@10
|
432 +2. an atom containing the invocation count
|
rlm@10
|
433 +3. the invocation count checker function
|
rlm@10
|
434 +4. a symbol of the name of the function being replaced."
|
rlm@10
|
435 + [fn-name expectation-hash]
|
rlm@10
|
436 + {:pre [(map? expectation-hash)
|
rlm@10
|
437 + (symbol? fn-name)]}
|
rlm@10
|
438 + (let [arg-checker (or (expectation-hash :has-args) (fn [& args] true))
|
rlm@10
|
439 + count-atom (atom 0)
|
rlm@10
|
440 + ret-fn (or
|
rlm@10
|
441 + (expectation-hash :calls)
|
rlm@10
|
442 + (fn [& args] (expectation-hash :returns)))]
|
rlm@10
|
443 + [(fn [& args]
|
rlm@10
|
444 + (has-matching-signature? fn-name args)
|
rlm@10
|
445 + (apply arg-checker fn-name args)
|
rlm@10
|
446 + (swap! count-atom inc)
|
rlm@10
|
447 + (apply ret-fn args))
|
rlm@10
|
448 + count-atom
|
rlm@10
|
449 + (or (expectation-hash :times) (fn [fn-name v] true))
|
rlm@10
|
450 + fn-name]))
|
rlm@10
|
451 +
|
rlm@10
|
452 +
|
rlm@10
|
453 +(defn validate-counts
|
rlm@10
|
454 + "given the sequence of all mock data for the expectation, simply calls the
|
rlm@10
|
455 +count checker for each dependency."
|
rlm@10
|
456 + [mock-data] (doseq [[mfn i checker fn-name] mock-data] (checker fn-name @i)))
|
rlm@10
|
457 +
|
rlm@10
|
458 +(defn- make-bindings [expect-bindings mock-data-sym]
|
rlm@10
|
459 + `[~@(interleave (map #(first %) (partition 2 expect-bindings))
|
rlm@10
|
460 + (map (fn [i] `(nth (nth ~mock-data-sym ~i) 0))
|
rlm@10
|
461 + (range (quot (count expect-bindings) 2))))])
|
rlm@10
|
462 +
|
rlm@10
|
463 +
|
rlm@10
|
464 +;;------------------------------------------------------------------------------
|
rlm@10
|
465 +;; These are convenience functions to improve the readability and use of this
|
rlm@10
|
466 +;; library. Useful in expressions such as:
|
rlm@10
|
467 +;; (expect [dep-fn1 (times (more-than 1) (returns 15)) etc)
|
rlm@10
|
468 +
|
rlm@10
|
469 +;; best used in the times function
|
rlm@10
|
470 +(defn once [x] (= 1 x))
|
rlm@10
|
471 +
|
rlm@10
|
472 +(defn never [x] (zero? x))
|
rlm@10
|
473 +
|
rlm@10
|
474 +(defn more-than [x] #(< x %))
|
rlm@10
|
475 +
|
rlm@10
|
476 +(defn less-than [x] #(> x %))
|
rlm@10
|
477 +
|
rlm@10
|
478 +(defn between [x y] #(and (< x %) (> y %)))
|
rlm@10
|
479 +
|
rlm@10
|
480 +;;best used in the has-args function
|
rlm@10
|
481 +(defn anything [x] true)
|
rlm@10
|
482 +
|
rlm@10
|
483 +
|
rlm@10
|
484 +;;------------------------------------------------------------------------------
|
rlm@10
|
485 +;; The following functions can be used to build up the expectation hash.
|
rlm@10
|
486 +
|
rlm@10
|
487 +(defn returns
|
rlm@10
|
488 + "Creates or associates to an existing expectation hash the :returns key with
|
rlm@10
|
489 +a value to be returned by the expectation after a successful invocation
|
rlm@10
|
490 +matching its expected arguments (if applicable).
|
rlm@10
|
491 +Usage:
|
rlm@10
|
492 +(returns ret-value expectation-hash?)"
|
rlm@10
|
493 +
|
rlm@10
|
494 + ([val] (returns val {}))
|
rlm@10
|
495 + ([val expectation-hash]
|
rlm@10
|
496 + {:pre [(map? expectation-hash)]}
|
rlm@10
|
497 + (assoc expectation-hash :returns val)))
|
rlm@10
|
498 +
|
rlm@10
|
499 +
|
rlm@10
|
500 +(defn calls
|
rlm@10
|
501 + "Creates or associates to an existing expectation hash the :calls key with a
|
rlm@10
|
502 +function that will be called with the given arguments. The return value from
|
rlm@10
|
503 +this function will be returned by the expected function. If both this
|
rlm@10
|
504 +and returns are specified, the return value of \"calls\" will have precedence.
|
rlm@10
|
505 +Usage:
|
rlm@10
|
506 +(calls some-fn expectation-hash?)"
|
rlm@10
|
507 +
|
rlm@10
|
508 + ([val] (calls val {}))
|
rlm@10
|
509 + ([val expectation-hash]
|
rlm@10
|
510 + {:pre [(map? expectation-hash)]}
|
rlm@10
|
511 + (assoc expectation-hash :calls val)))
|
rlm@10
|
512 +
|
rlm@10
|
513 +
|
rlm@10
|
514 +(defmacro has-args
|
rlm@10
|
515 + "Creates or associates to an existing expectation hash the :has-args key with
|
rlm@10
|
516 +a value corresponding to a function that will either return true if its
|
rlm@10
|
517 +argument expectations are met or throw an exception with the details of the
|
rlm@10
|
518 +first failed argument it encounters.
|
rlm@10
|
519 +Only specify as many predicates as you are interested in verifying. The rest
|
rlm@10
|
520 +of the values are safely ignored.
|
rlm@10
|
521 +Usage:
|
rlm@10
|
522 +(has-args [arg-pred-1 arg-pred-2 ... arg-pred-n] expectation-hash?)"
|
rlm@10
|
523 +
|
rlm@10
|
524 + ([arg-pred-forms] `(has-args ~arg-pred-forms {}))
|
rlm@10
|
525 + ([arg-pred-forms expectation-hash]
|
rlm@10
|
526 + {:pre [(vector? arg-pred-forms)
|
rlm@10
|
527 + (map? expectation-hash)]}
|
rlm@10
|
528 + `(assoc ~expectation-hash :has-args
|
rlm@10
|
529 + (make-arg-checker ~arg-pred-forms '~arg-pred-forms))))
|
rlm@10
|
530 +
|
rlm@10
|
531 +
|
rlm@10
|
532 +(defmacro times
|
rlm@10
|
533 + "Creates or associates to an existing expectation hash the :times key with a
|
rlm@10
|
534 +value corresponding to a predicate function which expects an integer value.
|
rlm@10
|
535 +Also, an integer can be specified, in which case the times will only be an
|
rlm@10
|
536 +exact match. The times check is called at the end of an expect expression to
|
rlm@10
|
537 +validate that an expected dependency function was called the expected
|
rlm@10
|
538 +number of times.
|
rlm@10
|
539 +Usage:
|
rlm@10
|
540 +(times n)
|
rlm@10
|
541 +(times #(> n %))
|
rlm@10
|
542 +(times n expectation-hash)"
|
rlm@10
|
543 + ([times-fn] `(times ~times-fn {}))
|
rlm@10
|
544 + ([times-fn expectation-hash]
|
rlm@10
|
545 + {:pre [(map? expectation-hash)]}
|
rlm@10
|
546 + `(assoc ~expectation-hash :times (make-count-checker ~times-fn '~times-fn))))
|
rlm@10
|
547 +
|
rlm@10
|
548 +
|
rlm@10
|
549 +;-------------------------------------------------------------------------------
|
rlm@10
|
550 +; The main expect macro.
|
rlm@10
|
551 +(defmacro expect
|
rlm@10
|
552 + "Use expect to redirect calls to dependent functions that are made within the
|
rlm@10
|
553 +code under test. Instead of calling the functions that would normally be used
|
rlm@10
|
554 +temporary stubs are used, which can verify function parameters and call counts.
|
rlm@10
|
555 +Return values of overridden functions can also be specified as needed.
|
rlm@10
|
556 +Usage:
|
rlm@10
|
557 +(expect [dep-fn (has-args [arg-pred1] (times n (returns x)))]
|
rlm@10
|
558 + (function-under-test a b c))"
|
rlm@10
|
559 +
|
rlm@10
|
560 + [expect-bindings & body]
|
rlm@10
|
561 + {:pre [(vector? expect-bindings)
|
rlm@10
|
562 + (even? (count expect-bindings))]}
|
rlm@10
|
563 + (let [mock-data (gensym "mock-data_")]
|
rlm@10
|
564 + `(let [~mock-data (map (fn [args#]
|
rlm@10
|
565 + (apply clojure.contrib.mock/make-mock args#))
|
rlm@10
|
566 + ~(cons 'list (map (fn [[n m]] (vector (list 'quote n) m))
|
rlm@10
|
567 + (partition 2 expect-bindings))))]
|
rlm@10
|
568 + (binding ~(make-bindings expect-bindings mock-data) ~@body)
|
rlm@10
|
569 + (clojure.contrib.mock/validate-counts ~mock-data) true)))
|