1

Is there any way to mock (not stub) a protocol function with Midje (clojure) using something like the "provided" syntax?

This is simial to the question in: Mocking Clojure protocols, but with mocking.

In more detail: I have a protocol and a function that returns something that implements it. I would like to stub the function to return a mock of the protocol and I would like to register an expectation on one of the functions of the mocked protocol "implementation".

edit - here is an example:

There is a protocol and it's implementation:

(defprotocol Thiny (go-bump [_ _]))
(deftype TheThing []
  Thiny
  (go-bump [_ _] 23))

There is a function that returns an implementation of a protocol:

(defn gimme [] (TheThing.))

TheThing might be a DB or network connection or some other nasty thing you want to get rid of in the test.

Then, there is the function I want to test:

(defn test-me [n]
  (let [t (gimme)]
    (-> t (go-bump n))))

I want to make sure it calls go-bump with n.

This is my first attempt to create a test. But it is only halfway done, I would like to setup expectations on the Thiny returned by gimme:

(fact
  (test-me 42) => 42
  (provided (gimme) => (reify Thiny (go-bump [_ n] n))))
Community
  • 1
  • 1
4ZM
  • 1,463
  • 1
  • 11
  • 21

2 Answers2

1

Mocking protocols should be no different than mocking functions, you need to consider that the first dispaching parameter is this and so the mocking function should take that type in consideration.

For instance, given a protocol P:

(defprotocol P 
  (foo [this]) 
  (bar-me [this] [this y]))

Extended for type Integer

(extend-protocol P
  Integer
  (foo [this]
    (+ this 4))
  (bar-me [this]
    (* this 5))
  (bar-me [this y]
    (+ this y)))

You can check a couple things first, there's no implementation for Long:

(foo 3)
=>  IllegalArgumentException No implementation of method: :foo of 
    protocol: #'P found for class: java.lang.Long  
    clojure.core/-cache-protocol-fn (core_deftype.clj:541)

Works as expected for ints:

(foo (int 3))
=> 7

Now define the fact and provide the protocol function as you need:

(fact
  (foo (int 3)) => 7
  (provided (foo 3) => 8))

In this case it properly fails since the mock is returning 8 instead of 7 for the specified input.

FAIL at (test.clj:20)
     Expected: 7
     Actual: 8

If value mocks are not enough and you need to provide an alternative implementation instead, take a look at with-redefs-fn, you can wrap tested functions with it.

 => (defn f [] false)
 => (println (f))
 ;; false
 => (with-redefs-fn {#'f (fn [] true)}
 #(println (f)))
 ;; true

There's also a discussion in GitHub about his with a couple alternatives for runtime dispatching mocks.

guilespi
  • 4,672
  • 1
  • 15
  • 24
  • Not sure I get how that solves my problem. I need to set up expectations on something returned from a function. Updated my question with an example. – 4ZM Oct 28 '13 at 15:02
  • Midje mockups are just data returning mockups, you specify the expected output for the input, but not an alternative implementation, there's also the problem that `provided` final value gets evaluated at compile time, so you would be evaluating a return value against a function. – guilespi Oct 28 '13 at 15:20
  • Checkout this couple articles about mocks: http://www.agilogy.com/blog/tdd-in-clojure-with-midje.html, http://squirrel.pl/blog/2011/02/12/tdd-in-clojure-mocking-stubbing/, http://notesonclojure.blogspot.com/2010/06/mocking-with-clojurecontribmock.html – guilespi Oct 28 '13 at 15:24
  • see edited `with-redefs-fn` sample for mocking out function implementations – guilespi Oct 28 '13 at 15:30
  • I'm still not getting it. Sorry. Mocking the gimme function is not the problem. The problem is setting up expectations on whatever it returns. Are you saying it can't be done? – 4ZM Oct 28 '13 at 15:33
  • it can be done, look at the articles I referenced, expectations on return value are set by the checks themselves: `(go-bump 2 1) => 4` mocking the go-bump function you either `(provided (go-bump 2 1) => 4)` (parameter value wise), or you redefine the `go-bump` function `with-redefs-fn` and make it do what you want – guilespi Oct 28 '13 at 15:45
  • I've read them, but this won't work:`(fact (test-me 42) => 42 (provided (go-bump ??? 42) => 42))`. What should the ??? part be? – 4ZM Oct 28 '13 at 15:49
  • Thanks for all your help! I figured it out. I'll add an update. – 4ZM Oct 28 '13 at 16:00
  • Look at https://github.com/marick/Midje/issues/17 Stu proposed alternative using `with-redefs-fn` – guilespi Oct 28 '13 at 16:02
1

For posterity. Here is a working test:

(fact
  (test-me 42) => 42
  (provided (gimme) => :the-thingy)
  (provided (go-bump :the-thingy 42) => 42))

The trick was to use multiple provided statements that tie into each other.

Wierd observation. The same way of testing doesn't work for a function that uses the other syntax for calling functions on the protocol. No idea why.

(defn test-me2 [n]
  (let [t (gimme)]
     (.go-bump t n)))
4ZM
  • 1,463
  • 1
  • 11
  • 21
  • Ok, you avoid creating the `dispatching object`, some of the proposals on the github issue were also about mocking `defrecord` – guilespi Oct 28 '13 at 16:06
  • This is an older question, but it's still relevant. Appareantly you cannot mock a function on a protocol, unless you also mock the creation of the object that creates the protocol implementation. Or something like that. – Wout Neirynck Sep 15 '17 at 08:52