amitrathore / conjure

a mocking library for clojure
http://s-expressions.com
73 stars 9 forks source link

Predicate matching for argument expectations #12

Open bilus opened 9 years ago

bilus commented 9 years ago

Thank you for the little library. Sometimes small is enough. :)

The thing I needed is verify-first-call-args-for but with more flexible argument matching. I've updated the macro (below) so while it's backwards-compatible, it adds predicate matching for args:

(defn xx [a b]
  10)

(mocking [xx]
  (xx 5 10)
  (verify-first-call-args-for-p xx __ __) ;; any arguments
  (verify-first-call-args-for-p xx 5 10) ;; values
  (verify-first-call-args-for-p xx pos? neg?)) ;; "Actual arguments do not match expectations" 

Of course you can mix and match.

If you're interested, I'll be happy to submit a pr.

UPDATE: Updated code below.

bilus commented 9 years ago

UPDATE: Added support for Regex, used protocols to make it extensible.

Example:

(defn xx [a b]
  10)
(defn y [s])

(deftest x-test
  (mocking [xx y]
    (xx 5 10)
    (verify-first-call-args-for-p xx __ __)                 ;; any arguments
    (verify-first-call-args-for-p xx 5 10)                  ;; values
    (verify-first-call-args-for-p xx pos? pos?)             ;; ifns
    (verify-first-call-args-for-p xx string? pos?)          ;; failing example
    ;; Actual arguments do not match expectations
    ;; Expected: (xx string? pos?)
    ;; Actual: (xx 5 10)
    (y "tata")
    (verify-first-call-args-for-p y string?)
    (verify-first-call-args-for-p y #"tata")        ;; regex
    (verify-first-call-args-for-p y #"sasa")        ;; failing example
    ;; Actual arguments do not match expectations
    ;; Expected: (y #"sasa")
    ;; Actual: (y "tata")
    ))

Implementation:


(defprotocol IArgMatcher
  (-matches-arg? [this x]))

(extend-type clojure.lang.IFn
  IArgMatcher
  (-matches-arg? [this x]
    (true? (this x))))

(extend-type java.util.regex.Pattern
  IArgMatcher
  (-matches-arg? [this x]
    (some? (re-matches this (str x)))))

(extend-type Object
  IArgMatcher
  (-matches-arg? [this x]
    (= this x)))

(defmacro verify-first-call-args-for-p
  "Asserts that the faked function was called at least once, and the first call
   was passed the args matching the specified predicates/values"
  [fn-name & args]
  `(do
     (assert-in-fake-context "verify-first-call-args-for-p")
     (assert-conjurified-fn "verify-first-call-args-for-p" ~fn-name)
     (when-not (pos? (count (get @call-times ~fn-name)))
       (do-report {:type     :fail, :message "Expected function not invoked"
                   :expected '(~fn-name ~@args)}))
     (let [actual-args# (first (get @conjure.core/call-times ~fn-name))
           arg-preds-match# (map -matches-arg? ~(vec args) actual-args#)]
       (when-not (every? true? arg-preds-match#)
         (do-report {:type     :fail, :message "Actual arguments do not match expectations"
                     :expected '(~fn-name ~@args), :actual (apply list '~fn-name actual-args#)})))))

(def __ (constantly true))
saulshanabrook commented 8 years ago

@bilus Where is the do-report function defined?

saulshanabrook commented 8 years ago

Oh never mind https://clojuredocs.org/clojure.test/do-report

bilus commented 8 years ago

:)