Closed bmabey closed 13 years ago
What I think: grumble
It's a definite issue. Still, sometimes I think Clojure isn't so much a leaky abstraction over the JVM as a powerful sump pump sucking Java badness into the world of Lisp.
Here's another example that I fooled with while trying to find a workaround:
(defprotocol FooProtocol
(strcat [this from-function]))
(defrecord FooRecord [from-record]
FooProtocol
(strcat [this from-function]
(str "record: " from-record " function: " from-function)))
(defn foo-record-user [record]
(strcat record "foo-record-user"))
(fact
(let [foo (FooRecord. "rec")]
(foo-record-user foo) => "record: rec function: foo-record-user"))
(fact
(let [foo (FooRecord. "rec")]
(foo-record-user foo) => "33"
(provided (strcat foo) => "33")))
Is something wrong with:
(defprotocol Foo (bar [_]))
(with-redefs [bar (fn [_] :no-problem)] (bar nil))
I had not seen with-redefs
.... is this only in 1.3?
I just pulled down 1.3.0-beta1
and tried it out. It appears to not work on protocol functions either:
user> (defprotocol Foo (bar [_]))
Foo
user> (defrecord FooRecord [x] Foo (bar [_] (inc x)))
user.FooRecord
user> (with-redefs [bar (fn [x] :blah)] (bar (FooRecord. 5)))
6
Am I doing something wrong?
It seems to work just fine with "regular" functions though:
user> (defn foo [x] (inc x))
#'user/foo
user> (with-redefs [foo dec] (foo 4))
3
It's just a matter of choosing the right thing to mock. Instead of mocking the function bar, you will need to mock the object FooRecord, e.g. using reify. The good news is that the approach should seem familiar to OO folks, who are always mocking the objects, not the fns. :-)
Since systems built around mock-based testing tend to expose plenty of injection points, I would expect this to be straightforward. If not, I would be happy to look at a larger example and propose ideas.
It is true that mocking plain fns and mocking objects will be two different things. That seems ok to me. Mocking is inherently a white-box exercise requiring that you deal with the specifics of your platform.
Yeah, I have been taking a similar approach in my tests. In certain cases I will define dummy objects (records) whose implementation I control so I can verify that the interactions are correct. However, this seems like a heavy-handed solution if all you need to do is stub a single function/method call for that record. For the use cases listed above this approach makes sense.
I'd like to beable to say something like (stub FooRecord foo #( ... ))
or . (stub foo-record-instance foo #( ... ))
I don't think this is possible because you will get IllegalArgumentException class user.FooRecord already directly implements interface user.Foo for protocol:#'user/Foo
. I think the only way to get behaviour like this would be to wrap the original foo-record-instance
in a temporary stub/mock proxy.
Side issue: You wrote, "Mocking is inherently a white-box exercise requiring that you deal with the specifics of your platform." That's not the style I'm trying to promote with Midje. I'm trying to make tests into statements about the logical relationship between functions. It's all about removing even more inessential [mental] complexity from the design than Clojure allows.
However, protocols are (or seem to me) a leaky abstraction, and I expect you're right that mocking objects is the right way to go about it. I might be able to unify the notation with some ideas about testing inner functions.
I'd prefer a non-unified notation that let me get at stuff, at least at the bottom. Will keep thinking about it...
I've been bashing against this much of the day, and I have one workaround:
defrecord
would look like:(clojure.core/defrecord FooRecord [from-record]
FooProtocol
(strcat [this from-function]
(if (oh_look_we_are_faked? behaviors.t-canary/strcat)
(apply behaviors.t-canary/strcat [this from-function])
...original body...)))
provided
can be unchanged. That just alter-var-root's the symbol naming the function to be a different function that records calls and provides faked values. So this works:(fact
(let [foo (FooRecord. "rec")]
(foo-record-user foo) => "33"
(provided (strcat foo "foo-record-user") => "33")))
This doesn't handle functions defined with extend-type
etc, but this is slimy enough that I don't want to go too far. If we can succeed with defrecord
, and get more people using network-of-facts-style testing, perhaps midje-ability might be a consideration for protocols going forward.
You'd have to do the same for deftype
as well...
I think I have a workable implementation that sticks close to the Midje notion of describing relationships between functions. I've posted about it on the mailing list. If you're not on there, I can excerpt it.
On Jun 23, 2011, at 8:28 AM, stuarthalloway wrote:
It's just a matter of choosing the right thing to mock. Instead of mocking the function bar, you will need to mock the object FooRecord, e.g. using reify. The good news is that the approach should seem familiar to OO folks, who are always mocking the objects, not the fns. :-)
Since systems built around mock-based testing tend to expose plenty of injection points, I would expect this to be straightforward. If not, I would be happy to look at a larger example and propose ideas.
It is true that mocking plain fns and mocking objects will be two different things. That seems ok to me. Mocking is inherently a white-box exercise requiring that you deal with the specifics of your platform.
Reply to this email directly or view it on GitHub: https://github.com/marick/Midje/issues/17#issuecomment-1425170
Brian Marick, Artisanal Labrador Contract programming in Ruby and Clojure Occasional consulting on Agile www.exampler.com, www.twitter.com/marick
General problem has solution, though there may still be unexplored corner cases.
Since function/method dispatch happens at the JVM layer for protocols I suspect the regular binding trick won't work. Here is an example of it not working:
Output:
I haven't played with this idea, but for midje (or any other clojure mocking tool for that matter) to support protocols wrapping a Java mocking library may be a viable option. WDYT?