view src/clojure/contrib/mock.clj.rej @ 10:ef7dbbd6452c

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