annotate 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
rev   line source
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)))