Permalink
Browse files

Check arity of functions being mocked (#433)

* ensure arity of function fake matches that of the function

* bump version

* add test for declared func

* add documentation
  • Loading branch information...
phillipm committed Jan 3, 2018
1 parent 6e5766c commit 3caa1305bc38b4261dee843f41338561e809f07b
View
@@ -2,21 +2,22 @@
This project adheres to [Semantic Versioning](http://semver.org/).
See [here](http://keepachangelog.com/) for the change log format.
## [1.9.2-alpha1] - 2017-12-27
## [1.9.2-alpha2] - 2018-01-03
- 431: __POTENTIALLY BREAKING__ Fix issue where you can fake functions with incorrect arities. Function faking now produces a test failure if you provide more or less arguments than expected by the function definition. This may break tests that incorrectly faked functions, but this is a good thing because it will reveal bugs in your tests.
## [1.9.2-alpha1] - 2017-12-27
- Print records in conventional way `#user.YourRecord {:arg-one 100}`, instead of `{:arg-one 100}::YourRecord`
- Fix upstream reflection warnings in `suchwow`
- Remove `clojure-commons` and `swiss-arrows` dependencies
## [1.9.1] - 2017-12-19
- pin specter version to fix import warning in suchwow
- use released version of clojure 1.9
## [1.9.0] - 2017-11-21
### Changed
- [POTENTIALLY BREAKING] Fail when `=contains=>` targets a non-map value, such as a vector, which has unclear semantics. A fact that looks like
- __POTENTIALLY BREAKING__ Fail when `=contains=>` targets a non-map value, such as a vector, which has unclear semantics. A fact that looks like
```clojure
(unfinished gen-list)
View
@@ -1,4 +1,4 @@
(defproject midje "1.9.2-alpha1"
(defproject midje "1.9.2-alpha2"
:description "A TDD library for Clojure that supports top-down ('mockish') TDD, encourages readable tests, provides a smooth migration path from clojure.test, balances abstraction and concreteness, and strives for graciousness."
:url "https://github.com/marick/Midje"
:pedantic? :warn
@@ -61,8 +61,6 @@
(defn record-end-of-prerequisite-call []
(swap! *call-action-count* update-in [(Thread/currentThread)] dec))
(defn- ^{:testable true} best-call-action
"Returns a fake: when one can handle the call
Else returns a function: from the first fake with a usable-default-function.
@@ -83,25 +81,80 @@
~@forms
(finally (record-end-of-prerequisite-call))))
(defn- valid-arg-counts
"Get required and optional argument counts for a function variable"
[function-var]
(let [arglists (:arglists (meta function-var))
[opt-arglists req-arglists] (seq/bifurcate #((set %) '&) arglists)]
[(set (map count req-arglists))
(and (seq opt-arglists) (apply min (map #(-> % count (- 2)) opt-arglists)))]))
(defn- correct-arg-count-message
"Readable message describing a function's valid required/optional arg counts"
[function-var]
(let [[req-arg-counts-set
opt-arg-min] (valid-arg-counts function-var)
req-counts (when (not (empty? req-arg-counts-set))
(clojure.string/join ", " (-> req-arg-counts-set seq sort)))]
(cond
(and req-counts opt-arg-min)
(str req-counts ", or " opt-arg-min "+ arguments")
(and (not req-counts) opt-arg-min)
(str opt-arg-min "+ arguments")
:else
(str req-counts " arguments"))))
(defn- correct-arg-count?
"Does the argument count provided fit one of the function's defined arities?"
[function-var provided-arg-count]
(if-let [arglists (:arglists (meta function-var))]
(let [[req-arg-counts-set
opt-arg-min] (valid-arg-counts function-var)
in-req-arglists? (req-arg-counts-set provided-arg-count)
in-opt-arglists? (and opt-arg-min
(>= provided-arg-count opt-arg-min))]
(or in-req-arglists?
in-opt-arglists?))
true))
(defn- fail-fake-arg-mismatch [function-var actual-args fakes]
(emit/fail {:type :prerequisite-arg-count-mismatches-implementation
:var function-var
:provided-arg-count (count actual-args)
:expected-arg-count-msg (correct-arg-count-message function-var)
:position (:position (first fakes))})
(format "`%s` returned this string because it was faked with an
incorrect number of arguments with respect to the function
implementation"
(+symbol function-var)))
(defn- fail-fake-unexpected-args [function-var actual-args fakes]
(emit/fail {:type :prerequisite-was-called-with-unexpected-arguments
:var function-var
:actual actual-args
:position (:position (first fakes))})
(format "`%s` returned this string because it was called with an unexpected argument"
(+symbol function-var)))
(defn- ^{:testable true} handle-mocked-call [function-var actual-args fakes]
(let [action (counting-nested-calls (best-call-action function-var actual-args fakes))]
(branch-on action
;; default fall-through for when :partial-prerequisites config is set
types/extended-fn?
(apply action actual-args)
;; standard case
map?
(do
(swap! (:call-count-atom action) inc)
((:result-supplier action)))
(if (correct-arg-count? function-var (count actual-args))
(do
(swap! (:call-count-atom action) inc)
((:result-supplier action)))
(fail-fake-arg-mismatch function-var actual-args fakes))
:else
(do
(emit/fail {:type :prerequisite-was-called-with-unexpected-arguments
:var function-var
:actual actual-args
:position (:position (first fakes))})
(format "`%s` returned this string because it was called with an unexpected argument"
(+symbol function-var))))))
(fail-fake-unexpected-args function-var actual-args fakes))))
;; Binding map related
@@ -103,6 +103,16 @@
" would be called with these arguments:")
(str " " (pretty-xs (:actual m)))))
(defmethod messy-lines :prerequisite-arg-count-mismatches-implementation [m]
(list
(str "You faked "
(prerequisite-var-description (:var m))
" with an argument count that doesn't match the function's defined arguements:")
(str "Provided "
(:provided-arg-count m)
" argument(s), where valid options are: "
(:expected-arg-count-msg m))))
(defmethod messy-lines :parse-error [m]
(list
(str " Midje could not understand something you wrote: ")
@@ -272,4 +272,12 @@
(provided
(second-est (first-est 1 5)) => 100))
(defn my-inc [a b] (- (+ a (inc b)) b))
(defn strange-adder [a b]
(+ (my-inc a) b))
(silent-fact
(strange-adder 1 2) => 300
(provided (my-inc 1) => 298))
(note-that fact-fails)
@@ -2,7 +2,7 @@
(:require [midje
[sweet :refer :all]
[test-util :refer :all]]
[midje.data.prerequisite-state :refer [binding-map implements-a-fake? usable-default-function?]]
[midje.data.prerequisite-state :refer [binding-map implements-a-fake? usable-default-function?] :as prereq-state]
[midje.test-util :refer :all]
[midje.parsing.2-to-lexical-maps.fakes :refer [fake]]
[midje.parsing.2-to-lexical-maps.data-fakes :refer [data-fake]]
@@ -234,6 +234,39 @@
(provided
(f (my-inc 1)) => 2))
(defn multi-arity-func
([a] a)
([a b] a))
(defn opts-func
([a] a)
([a b & opts] a))
(defn single-arity-func
[a] a)
(unfinished unfinished-func)
(declare declared-func)
(fact "check if provided arg count matches one of the function's arglists"
(#'prereq-state/correct-arg-count? #'single-arity-func 0) => falsey
(#'prereq-state/correct-arg-count? #'single-arity-func 1) => truthy
(#'prereq-state/correct-arg-count? #'single-arity-func 2) => falsey
(#'prereq-state/correct-arg-count? #'multi-arity-func 0) => falsey
(#'prereq-state/correct-arg-count? #'multi-arity-func 1) => truthy
(#'prereq-state/correct-arg-count? #'multi-arity-func 2) => truthy
(#'prereq-state/correct-arg-count? #'multi-arity-func 3) => falsey
(#'prereq-state/correct-arg-count? #'opts-func 0) => falsey
(#'prereq-state/correct-arg-count? #'opts-func 1) => truthy
(#'prereq-state/correct-arg-count? #'opts-func 2) => truthy
(#'prereq-state/correct-arg-count? #'opts-func 3) => truthy
(#'prereq-state/correct-arg-count? #'unfinished-func 1) => truthy
(#'prereq-state/correct-arg-count? #'unfinished-func 0) => truthy
(#'prereq-state/correct-arg-count? #'declared-func 0) => truthy
(#'prereq-state/correct-arg-count? #'declared-func 10) => truthy)
;;; DO NOT DELETE
;;; These are used to test the use of vars to fake private functions
View
@@ -158,7 +158,10 @@
(provided
(g (h (i (j 1)))) => 3))
(defn second-arg [a b] (+ a (f a (g b))))
(defn f [n m] 100)
(defn second-arg [a b]
(+ a (f a (g b))))
(fact
(second-arg 1 2) => 9
(provided

0 comments on commit 3caa130

Please sign in to comment.