marick / Midje

Midje provides a migration path from clojure.test to a more flexible, readable, abstract, and gracious style of testing
MIT License
1.68k stars 128 forks source link

Provided mock fails when called twice #122

Closed kurtharriger closed 12 years ago

kurtharriger commented 12 years ago
(use 'midje.sweet)

(defn mocked [x] (Exception. "should not be called"))
(defn withmocked [x] (mocked (inc x)))

;.;. FAIL at (NO_SOURCE_FILE:1)
;.;.     Expected: 3
;.;.       Actual: #<Exception java.lang.Exception: should not be called>
(facts "Fails if mocked is called twice"
  (let [x 2]
    (mocked 3) => 3
    (withmocked x) => 3
    )
  (provided
    (mocked 3) => 3
    ))

(facts "But works if I comment out the first fact"
  (let [x 2]
    (mocked 3) => 3
;;    (withmocked x) => 3
    )
  (provided
    (mocked 3) => 3
    ))

(facts "Or if I comment out the second fact"
  (let [x 2]
;;    (mocked 3) => 3
    (withmocked x) => 3
    )
  (provided
    (mocked 3) => 3
    ))

(facts "Or use with-redefs instead of provided"
  (with-redefs [mocked (fn [x] 3)]
    (let [x 2]
      (mocked 3) => 3
      (withmocked x) => 3
      )))
AlexBaranosky commented 12 years ago
(let x 2 => 3

That isn't legal code.

AlexBaranosky commented 12 years ago

This is supposed to fail because you didn't call mocked

(facts "Or if I comment out the second fact"
  (let [x 2]
    ;; (mocked 3) => 3
    (withmocked x) => 3)
  (provided
    (mocked 3) => 3))
AlexBaranosky commented 12 years ago

Did you intend for this:

(defn mocked x)
(defn withmocked x))

to be this?:

(defn mocked [x] x)

(defn withmocked [x] 
  (mocked x))
kurtharriger commented 12 years ago

Sorry github processed the code as markdown or something.. created a gist instead https://gist.github.com/2394051

Only the first fact fails, all others pass.

AlexBaranosky commented 12 years ago

I think this will work as you expect:

(facts "Fails if mocked is called twice"
  (let [x 2]  
    (mocked 3) => 3
    (withmocked x) => 3)
  (against-background
    (mocked 3) => 3))

provided only applies to the check ( (x => y) ) that occurs right before it. against-background applies to every check in scope.

kurtharriger commented 12 years ago

Yes, that does work... I've never used against-background before perhaps I need to have a second look at it.
Thanks!

kurtharriger commented 12 years ago

(against-backround) wasn't quite what I was looking for my scenario as I was more or less trying to validate my ring request handlers were being called properly so I want to expect they are called with the desired parameters :times 1. I thought the problem was that it was the it was only mocking the first call, but the problem had more to due with how I was structuring my validations using let bindings instead of (contains).

(fact "Fails: mocks do not work in let bindings" (let [request {:params {:id 3}} response (handler 1)](:status response) => 200 (:body response) => "OK") (provided (mocked 3) => "OK" :times 1 ))

This doesn't work with provides, even for a single call because the let bindings are evaluated before the fakes have been setup.

;;. [31mFAIL[0m at (NO_SOURCE_FILE:1) ;;. Expected: 200 ;;. Actual: 500 ;;. ;;. [31mFAIL[0m at (NO_SOURCE_FILE:1) ;;. You claimed the following was needed, but it was never used: ;;. (mocked 3) ;;. ;;. [31mFAIL[0m at (NO_SOURCE_FILE:1) ;;. Expected: "OK" ;;. Actual: "ERROR: THIS SHOULDN'T HAVE BEEN CALLED"

And the reason is because the let bindings (comment (do (midje.util.report/fact-begins) (midje.internal-ideas.wrapping/midje-wrapped (midje.internal-ideas.fakes/with-installed-fakes (midje.ideas.background/background-fakes) (do fact "Fails: mocks do not work in let bindings" (let [request {:params {:id 3}} response (handler 1)](midje.internal-ideas.wrapping/midje-wrapped %28midje.semi-sweet/expect %28:status response%29 => 200 :position %28midje.internal-ideas.file-position/line-number-known 1%29%29) (midje.internal-ideas.wrapping/midje-wrapped (midje.semi-sweet/expect (:body response) => "OK" :position (midje.internal-ideas.file-position/line-number-known 1) (midje.semi-sweet/fake (mocked 3) => "OK" :position (midje.internal-ideas.file-position/line-number-known 1) :times 1))))))) (midje.util.report/fact-checks-out?)) )

I spent some more time reading the documentation and from what I can see the "correct" way to do this from what I can tell is using contains:

(fact "This works, but IMHO is more difficult to read when validating multiple properties" (let [request {:params {:id 3}}](handler request)) => (contains {:status 200 :body "OK" :content-type "text/plain"}) (provided (mocked 3) => "OK" :times 1 ))

IMHO the let binding syntax is more straight forward and for newbies like myself to learn. Interestingly enough I also found this initial issue reporting let bindings not working within facts https://github.com/marick/Midje/issues/1 and perhaps irronically I wouldn't have run across this problem if fact was inside of let bindings rather than outside of them as I wouldn't have expected expectations at a deeper nesting level to work, however fact then let leads me to believe that provides applies to entire fact including let bindings. Anyway (contains) does as far as I can tell does do everything I need, I just thought I would provide some additional feedback on my learning experience. The rest of this example is here: https://gist.github.com/2395697

Great work on this, very useful project indeed.

Thanks!