Hi folks. So I''m using mocha on a ruby project, and I really like it. One thing I''ve noticed is that it can be a bit "surprising" when I''m mocking methods that don''t exist on an object, and I think there''s an easyish fix. At the moment, you use sheep = mock() to create a mock sheep. What I''d like to do is something like sheep = mock(Sheep), so the mock is typed. It''s still duck-typed because the Sheep class is open, so I can set expectations on it like this: sheep.expects(:chew).with(''grass'') and then use the sheep somewhere. But here''s the cool bit. When I invoke an expected method on the mock sheep (i.e. a method that matches an expectation), if the sheep is typed, it checks to see whether the type responds to the method (or if it implements method_missing). If neither of these is the case, it blows up like a regular sheep with a NoMethodError, rather than doing the expectation stuff. The reason for deferring the failure until the method is invoked rather than failing fast like JMock does when the expectation is defined, is in case the method gets defined, or the Sheep class gets extended, between the mock being defined and the method being invoked. Again, you would only get this behaviour if you created a typed mock, using sheep = mock(Sheep) rather than sheep = mock(). What do you think? Cheers, Dan ps. James: so that''s where you got to!
On 3/6/07, Dan North <dan at tastapod.com> wrote:> Hi folks. > > So I''m using mocha on a ruby project, and I really like it. One thing > I''ve noticed is that it can be a bit "surprising" when I''m mocking > methods that don''t exist on an object, and I think there''s an easyish fix. > > At the moment, you use sheep = mock() to create a mock sheep. What I''d > like to do is something like sheep = mock(Sheep), so the mock is typed. > It''s still duck-typed because the Sheep class is open, so I can set > expectations on it like this: > > sheep.expects(:chew).with(''grass'') > > and then use the sheep somewhere. But here''s the cool bit. > > When I invoke an expected method on the mock sheep (i.e. a method that > matches an expectation), if the sheep is typed, it checks to see whether > the type responds to the method (or if it implements method_missing). If > neither of these is the case, it blows up like a regular sheep with a > NoMethodError, rather than doing the expectation stuff. > > The reason for deferring the failure until the method is invoked rather > than failing fast like JMock does when the expectation is defined, is in > case the method gets defined, or the Sheep class gets extended, between > the mock being defined and the method being invoked. > > Again, you would only get this behaviour if you created a typed mock, > using sheep = mock(Sheep) rather than sheep = mock(). > > What do you think?We''ve got an open feature request for this in rspec as well, but with the ability to configure a test run to either report or not report these errors. The idea being that reporting that Sheep won''t respond to certain messages can be a distraction from the work you''re doing on Shepherd, but then when the task at hand reaches a good breaking point, you can run your tests w/ some flag that says "please tell me all the messages that I''ve mocked that I must now implement". I''d love to see this feature in mocha because it seems really useful in a test driven world, and because the day is not far off when rspec will support using mocha in addition to its own mock framework. The closer to parity of features we can get, the more likely people will be interested in using mocha within rspec. Cheers, David> > Cheers, > Dan > > ps. James: so that''s where you got to! > _______________________________________________ > mocha-developer mailing list > mocha-developer at rubyforge.org > http://rubyforge.org/mailman/listinfo/mocha-developer >
On 06/03/07, Dan North <dan at tastapod.com> wrote:> > So I''m using mocha on a ruby project, and I really like it. One thing > I''ve noticed is that it can be a bit "surprising" when I''m mocking > methods that don''t exist on an object, and I think there''s an easyish fix. > > At the moment, you use sheep = mock() to create a mock sheep. What I''d > like to do is something like sheep = mock(Sheep), so the mock is typed. > It''s still duck-typed because the Sheep class is open, so I can set > expectations on it like this: > > sheep.expects(:chew).with(''grass'') > > and then use the sheep somewhere. But here''s the cool bit. > > When I invoke an expected method on the mock sheep (i.e. a method that > matches an expectation), if the sheep is typed, it checks to see whether > the type responds to the method (or if it implements method_missing). If > neither of these is the case, it blows up like a regular sheep with a > NoMethodError, rather than doing the expectation stuff. > > The reason for deferring the failure until the method is invoked rather > than failing fast like JMock does when the expectation is defined, is in > case the method gets defined, or the Sheep class gets extended, between > the mock being defined and the method being invoked. > > Again, you would only get this behaviour if you created a typed mock, > using sheep = mock(Sheep) rather than sheep = mock(). > > What do you think? > > Cheers, > Dan > > ps. James: so that''s where you got to! >Hi Dan, Good to hear from you. Something very close to this idea has been on the to-do list for a long time, but I don''t think we had thought of deferring the check to respond_to? until the actual moment the method on the mock is called. Good idea. Of course you''d be very welcome to send us a patch! My understanding of Ruby is that if you implement method_missing on a class, you should really implement respond_to? so that it reflects the methods that method_missing can handle. Is that your understanding? I know that not all Ruby libraries do this (notably ActiveRecord, at least when I last looked some months ago). Are you using Ruby in anger on a project? -- James. http://blog.floehopper.org
Hi Dan, David, and any other interested parties, I''ve just been looking into implementing this and have come up with a few issues. It''d be great to have some feedback. In Dan''s sheep example, you want to constrain the mock to only respond like an instance of the Sheep class. For completeness I think we should allow you to constrain the mock to only respond like the Sheep class itself. I''ve been playing with some possible syntax... sheep = mock() sheep.responds_like(instance_of(Sheep)) sheep_class = mock sheep_class.responds_like(Sheep) As you can see I''m also inclined to avoid overloading the parameters of the mock() method any more - hence the responds_like() method. What do you think about the instance_of() method? What do you think about the responds_like() method? I like Dan''s idea of raising a NoMethodError at invoke time, but I''m wondering if I should extend the message slightly to avoid confusion if you''ve actually set up an expectation for the non-existent method... sheep = mock() sheep.responds_like(instance_of(Sheep)) sheep.expects(:foo).with(:bar) sheep.foo # => NoMethodError: undefined method `foo'' for #<Mock:0x456edc> which responds like instance_of(Sheep) What do you think about the extension to the exception message: "which responds like instance_of(Sheep)"? -- James. http://blog.floehopper.org
On 4/25/07, James Mead <jamesmead44 at gmail.com> wrote:> Hi Dan, David, and any other interested parties, > > I''ve just been looking into implementing this and have come up with a > few issues. It''d be great to have some feedback. > > In Dan''s sheep example, you want to constrain the mock to only respond > like an instance of the Sheep class. For completeness I think we > should allow you to constrain the mock to only respond like the Sheep > class itself. I''ve been playing with some possible syntax... > > sheep = mock() > sheep.responds_like(instance_of(Sheep)) > > sheep_class = mock > sheep_class.responds_like(Sheep)How about .... sheep = mock() sheep.responds_like(Sheep) sheep_class = mock sheep_class.responds_like(Sheep.class)> > As you can see I''m also inclined to avoid overloading the parameters > of the mock() method any more - hence the responds_like() method. > > What do you think about the instance_of() method? > What do you think about the responds_like() method? > > I like Dan''s idea of raising a NoMethodError at invoke time, but I''m > wondering if I should extend the message slightly to avoid confusion > if you''ve actually set up an expectation for the non-existent > method... > > sheep = mock() > sheep.responds_like(instance_of(Sheep)) > sheep.expects(:foo).with(:bar) > sheep.foo # => NoMethodError: undefined method `foo'' for > #<Mock:0x456edc> which responds like instance_of(Sheep) > > What do you think about the extension to the exception message: "which > responds like instance_of(Sheep)"?Based on my suggestion above, I''d like "which responds like Sheep" or "which response like Sheep.class". This is all just a matter of detail and style. However it lands, I do like the overall idea of having #responds_like and having support for classes and instances. The suggestion I''m making seems to align more w/ the notion of "class as object" (i.e. we''re either dealing w/ an instance of Sheep or an instance of Sheep.class - but either way we''re talking about how an instance of something behaves). WDYT? Cheers, David> > -- > James. > http://blog.floehopper.org > _______________________________________________ > mocha-developer mailing list > mocha-developer at rubyforge.org > http://rubyforge.org/mailman/listinfo/mocha-developer >
Hmm. Interesting. I think I find my examples more readable/logical than the ones you give, but it''s helpful to have someone else''s take on the matter. Also wouldn''t there be difficulties implementing the responds_like(Sheep.class) version, in that the class() method on any class object will return Class e.g. Sheep.class # => Class and Goat.class # => Class. An example I forgot to mention... sheep = mock() sheep.responds_like(Sheep.new) So responds_like(responder) just needs a responder that responds to respond_to?() if you see what I mean. The instance_of() method would be provided so you didn''t have to construct an instance of an object just so you could mock it. -- James. http://blog.floehopper.org
On 4/25/07, James Mead <jamesmead44 at gmail.com> wrote:> Hmm. Interesting. I think I find my examples more readable/logical > than the ones you give, but it''s helpful to have someone else''s take > on the matter. > > Also wouldn''t there be difficulties implementing the > responds_like(Sheep.class) version, in that the class() method on any > class object will return Class e.g. Sheep.class # => Class and > Goat.class # => Class.Bummer - was just thinking about the outside view - this does present a problem.> > An example I forgot to mention... > > sheep = mock() > sheep.responds_like(Sheep.new) > > So responds_like(responder) just needs a responder that responds to > respond_to?() if you see what I mean. The instance_of() method would > be provided so you didn''t have to construct an instance of an object > just so you could mock it.I think the thing that led me to Sheep/Sheep.class is the lack of symmetry that instance_of(Sheep) introduces. I guess I''d rather have the instance be the default and have a special way to indicate that I''m interested in class methods: sheep = mock() sheep.responds_like(Sheep) sheep_class = mock sheep_class.responds_like(class_of(Sheep)) I don''t like #class_of, but you get the idea. How about this? sheep = mock() sheep.responds_like(Sheep, :instance) sheep_class = mock sheep_class.responds_like(Sheep, :class) ... and have :instance be the default so you can say responds_like(Sheep) for instances????? Or this? sheep = mock() sheep.responds_like(Sheep) sheep_class = mock sheep_class.responds_like_class(Sheep) Actually, I think this would be the easiest to implement, too. #responds_like(klass) could ask for klass''s public_instance_methods and #responds_like_class(klass) could ask for klass''s public_class_methods. If, on the other hand, supporting instance_of(Sheep), responds_like(responder) has to treat responder differently if it is (instance_of(Sheep)) or just (Sheep). In the case of just Sheep, it could ask for it''s public_class_methods. In the case of instance_of(Sheep) it would have to ask for something else - or you''d have to override public_class_methods on the object returned by instance_of(Sheep) to return the sheep''s public_instance_methods - which just doesn''t seem right to me. WDYT? David> > -- > James. > http://blog.floehopper.org > _______________________________________________ > mocha-developer mailing list > mocha-developer at rubyforge.org > http://rubyforge.org/mailman/listinfo/mocha-developer >
If I had a vote, I''d vote for this one... sheep.responds_like(Sheep, :instance) sheep_class.responds_like(Sheep, :class) On 4/25/07, David Chelimsky <dchelimsky at gmail.com> wrote:> On 4/25/07, James Mead <jamesmead44 at gmail.com> wrote: > > Hmm. Interesting. I think I find my examples more readable/logical > > than the ones you give, but it''s helpful to have someone else''s take > > on the matter. > > > > Also wouldn''t there be difficulties implementing the > > responds_like(Sheep.class) version, in that the class() method on any > > class object will return Class e.g. Sheep.class # => Class and > > Goat.class # => Class. > > Bummer - was just thinking about the outside view - this does present a problem. > > > > > An example I forgot to mention... > > > > sheep = mock() > > sheep.responds_like(Sheep.new) > > > > So responds_like(responder) just needs a responder that responds to > > respond_to?() if you see what I mean. The instance_of() method would > > be provided so you didn''t have to construct an instance of an object > > just so you could mock it. > > I think the thing that led me to Sheep/Sheep.class is the lack of > symmetry that instance_of(Sheep) introduces. I guess I''d rather have > the instance be the default and have a special way to indicate that > I''m interested in class methods: > > sheep = mock() > sheep.responds_like(Sheep) > > sheep_class = mock > sheep_class.responds_like(class_of(Sheep)) > > I don''t like #class_of, but you get the idea. > > How about this? > > sheep = mock() > sheep.responds_like(Sheep, :instance) > > sheep_class = mock > sheep_class.responds_like(Sheep, :class) > > ... and have :instance be the default so you can say > responds_like(Sheep) for instances????? > > Or this? > > sheep = mock() > sheep.responds_like(Sheep) > > sheep_class = mock > sheep_class.responds_like_class(Sheep) > > Actually, I think this would be the easiest to implement, too. > #responds_like(klass) could ask for klass''s public_instance_methods > and #responds_like_class(klass) could ask for klass''s > public_class_methods. > > If, on the other hand, supporting instance_of(Sheep), > responds_like(responder) has to treat responder differently if it is > (instance_of(Sheep)) or just (Sheep). In the case of just Sheep, it > could ask for it''s public_class_methods. In the case of > instance_of(Sheep) it would have to ask for something else - or you''d > have to override public_class_methods on the object returned by > instance_of(Sheep) to return the sheep''s public_instance_methods - > which just doesn''t seem right to me. > > WDYT? > > David > > > > -- > > James. > > http://blog.floehopper.org > > _______________________________________________ > > mocha-developer mailing list > > mocha-developer at rubyforge.org > > http://rubyforge.org/mailman/listinfo/mocha-developer > > > _______________________________________________ > mocha-developer mailing list > mocha-developer at rubyforge.org > http://rubyforge.org/mailman/listinfo/mocha-developer >-- Virtually, Ned Wolpert "Settle thy studies, Faustus, and begin..." --Marlowe Discere docendo...
> In the case of just Sheep, it > could ask for it''s public_class_methods. In the case of > instance_of(Sheep) it would have to ask for something else - or you''d > have to override public_class_methods on the object returned by > instance_of(Sheep) to return the sheep''s public_instance_methods - > which just doesn''t seem right to me.How about: # instance methods sheep = mock sheep.restricted_to Sheep.instance_methods or # class methods sheep = mock sheep.restricted_to Sheep.singleton_methods It''s a bit more verbose, but more readable in my opinion. Also, I''m not sold on responds_to or restricted_to. I don''t find that either one conveys the intent I''m looking for. Thoughts? Jay
On 4/27/07, Jay Fields <jay at jayfields.com> wrote:> > In the case of just Sheep, it > > could ask for it''s public_class_methods. In the case of > > instance_of(Sheep) it would have to ask for something else - or you''d > > have to override public_class_methods on the object returned by > > instance_of(Sheep) to return the sheep''s public_instance_methods - > > which just doesn''t seem right to me. > > How about: > > # instance methods > sheep = mock > sheep.restricted_to Sheep.instance_methods > > or > > # class methods > sheep = mock > sheep.restricted_to Sheep.singleton_methods > > It''s a bit more verbose, but more readable in my opinion. Also, I''m > not sold on responds_to or restricted_to. I don''t find that either > one conveys the intent I''m looking for. > > Thoughts?I love the syntax, though I''d make it a command rather than a "state"-ment: # instance methods sheep = mock sheep.restrict_to Sheep.instance_methods # class methods sheep = mock sheep.restrict_to Sheep.singleton_methods The other problem is related to Dan''s initial point about classes that are extended or modified at runtime. If you just pass an Array to #restrict_to then it has no knowledge of the type we''re interested in, so methods added downstream wouldn''t be discoverable. With that in mind, I propose: # instance methods sheep = mock sheep.restrict_to Sheep, :instance_methods # class methods sheep = mock sheep.restrict_to Sheep, :singleton_methods That keeps the readability Jay proposes and supports the late binding we''re looking for. More thoughts?
On 27/04/07, Jay Fields <jay at jayfields.com> wrote:> > How about: > > # instance methods > sheep = mock > sheep.restricted_to Sheep.instance_methods > > or > > # class methods > sheep = mock > sheep.restricted_to Sheep.singleton_methods > > It''s a bit more verbose, but more readable in my opinion. Also, I''m > not sold on responds_to or restricted_to. I don''t find that either > one conveys the intent I''m looking for. >I was suggesting "responds_like", not "responds_to" or "restricted_to". I don''t think the #instance_methods & #singleton_methods style is quite right. e.g. what if #method_missing accepts method names matching a particular pattern. In this case #respond_to? should return true for any method names that #method_missing would handle, but there is no equivalent requirement for #instance_methods to include all matching method names (in fact this list could theoretically be infinite). An obvious example of this would be ActiveRecord''s find_by_xxx_and_yyy type methods. It''s not a great example, because when I last checked ActiveRecord didn''t implement #respond_to? correctly, but that could be fixed! So I think #respond_to? is the right method to be querying. Does this make sense? If not, can you put your finger on what exactly it is you don''t like? Here are some fuller examples that may make things clearer... class Sheep def chew(food); end def self.number_of_legs; 4 end end sheep = mock() sheep.responds_like(Sheep.new) assert sheep.respond_to?(:chew) sheep.expects(:chew).with(''grass'') sheep.chew(''grass'') sheep_class = mock sheep_class.responds_like(Sheep) assert sheep_class.respond_to?(Sheep) sheep_class.stubs(:number_of_legs).returns(2) assert_equal 2, sheep_class.number_of_legs If constructing a Sheep instance in the test is undesirable, use the #instance_of method... sheep = mock() sheep.responds_like(instance_of(Sheep)) assert sheep.respond_to?(:chew) sheep.expects(:chew).with(''grass'') sheep.chew(''grass'') This last example is the one I''m least happy with, especially as it may well be the most common scenario. Any more ideas? Thanks. -- James. http://blog.floehopper.org
On 28/04/07, David Chelimsky <dchelimsky at gmail.com> wrote:> > I love the syntax, though I''d make it a command rather than a > "state"-ment: > > # instance methods > sheep = mock > sheep.restrict_to Sheep.instance_methods > > # class methods > sheep = mock > sheep.restrict_to Sheep.singleton_methods > > The other problem is related to Dan''s initial point about classes that > are extended or modified at runtime. If you just pass an Array to > #restrict_to then it has no knowledge of the type we''re interested in, > so methods added downstream wouldn''t be discoverable. With that in > mind, I propose: > > # instance methods > sheep = mock > sheep.restrict_to Sheep, :instance_methods > > # class methods > sheep = mock > sheep.restrict_to Sheep, :singleton_methods > > That keeps the readability Jay proposes and supports the late binding > we''re looking for. > > More thoughts? >As I explained in my reply to Jay''s email, I think #respond_to? is the better method to think about than any of the method-listing methods like #instance_methods. I know your last suggestion doesn''t actually use those method-listing methods, but it does use that terminology which I think I''d like to avoid. Do you see what I''m getting at? -- James. http://blog.floehopper.org
This is timely for me. I''ve been thinking about exactly this vocabulary recently, but in the context of BDD rather than (as well as?) mocking. It''s about the terms "is a" and "has a" and how they restrict your thinking because they don''t really cover the idea of roles. You need "acts like a" or "behaves like a" to really describe multiple hats. In this instance, I would like something like "acts like a" (in fact, David and I were discussing "quacks_like" in another conversation). Here''s some thinking-out-loud: So: mock_sheep.responds_like(Sheep.new) or mock_sheep.acts_like(Sheep.new) This should be additive: mock_ninja_sheep.acts_like(Sheep.new) mock_ninja_sheep.acts_like(Ninja.new) or even: mock_ninja_sheep.acts_like(Sheep.new, Ninja.new) One problem I see is a hard-to-detect semantic error: mock_sheep.acts_like(Sheep) # oops - meant Sheep.new Also, what about modules? This is fugly: mock_countable_sheep.acts_like(Sheep.new.extend(Enumerable)) # yuk So, we need a way of saying: "fail intuitively if the mocked object wouldn''t respond to this call". Ok, end of ramble. Here''s my ill-thought-out proposal: mock_sheep = mock("sheep") # give it a name mock_sheep.responds_like_type(Sheep, ...) # uses #instance_method mock_sheep.responds_like_instance(ninja, ...) # uses #respond_to? mock_sheep.responds_like(cheese) # depends if cheese is class/module The responds_like_type version could even check if type implements method_missing. The failure mode here would be if you wanted to mock the /instance/ methods of a /class/, using responds_like(type), but that seems very much like an edge case, and you could explicitly use responds_like_instance(type) anyway. Of course, all these checks would be done at method invocation time, so late binding would work. Thoughts? David Chelimsky wrote:> On 4/27/07, Jay Fields <jay at jayfields.com> wrote: > >>> In the case of just Sheep, it >>> could ask for it''s public_class_methods. In the case of >>> instance_of(Sheep) it would have to ask for something else - or you''d >>> have to override public_class_methods on the object returned by >>> instance_of(Sheep) to return the sheep''s public_instance_methods - >>> which just doesn''t seem right to me. >>> >> How about: >> >> # instance methods >> sheep = mock >> sheep.restricted_to Sheep.instance_methods >> >> or >> >> # class methods >> sheep = mock >> sheep.restricted_to Sheep.singleton_methods >> >> It''s a bit more verbose, but more readable in my opinion. Also, I''m >> not sold on responds_to or restricted_to. I don''t find that either >> one conveys the intent I''m looking for. >> >> Thoughts? >> > > I love the syntax, though I''d make it a command rather than a "state"-ment: > > # instance methods > sheep = mock > sheep.restrict_to Sheep.instance_methods > > # class methods > sheep = mock > sheep.restrict_to Sheep.singleton_methods > > The other problem is related to Dan''s initial point about classes that > are extended or modified at runtime. If you just pass an Array to > #restrict_to then it has no knowledge of the type we''re interested in, > so methods added downstream wouldn''t be discoverable. With that in > mind, I propose: > > # instance methods > sheep = mock > sheep.restrict_to Sheep, :instance_methods > > # class methods > sheep = mock > sheep.restrict_to Sheep, :singleton_methods > > That keeps the readability Jay proposes and supports the late binding > we''re looking for. > > More thoughts? > _______________________________________________ > mocha-developer mailing list > mocha-developer at rubyforge.org > http://rubyforge.org/mailman/listinfo/mocha-developer >
Wow - it''s great to have so much input on this... On 30/04/07, Dan North <dan at tastapod.com> wrote:> This is timely for me. I''ve been thinking about exactly this vocabulary> recently, but in the context of BDD rather than (as well as?) mocking. > It''s about the terms "is a" and "has a" and how they restrict your > thinking because they don''t really cover the idea of roles. You need > "acts like a" or "behaves like a" to really describe multiple hats. > > In this instance, I would like something like "acts like a" (in fact, > David and I were discussing "quacks_like" in another conversation).I really like "quacks_like". :-)> Here''s some thinking-out-loud: > > So: > mock_sheep.responds_like(Sheep.new) > or > mock_sheep.acts_like(Sheep.new) > > This should be additive: > mock_ninja_sheep.acts_like(Sheep.new) > mock_ninja_sheep.acts_like(Ninja.new) > or even: > mock_ninja_sheep.acts_like(Sheep.new, Ninja.new)I''m not clear why you would want to be able to do this if Sheep & Ninja are distinct classes.> One problem I see is a hard-to-detect semantic error: > mock_sheep.acts_like(Sheep) # oops - meant Sheep.newAgreed.> Also, what about modules? This is fugly: > mock_countable_sheep.acts_like(Sheep.new.extend(Enumerable)) # yukA good point, but this would only be an issue if you were extending a Sheep instance at runtime. If the Sheep class includes Enumerable at class definition time, any Sheep instance will respond_to? Enumerable methods. However, it would be nice to have a solution that covers extending a Sheep instance at runtime. I''m not so sure that there is anything wrong with your "yuk" example given that it is a bit of an edge case.> So, we need a way of saying: "fail intuitively if the mocked object > wouldn''t respond to this call". > > Ok, end of ramble. Here''s my ill-thought-out proposal: > > mock_sheep = mock("sheep") # give it a name > mock_sheep.responds_like_type(Sheep, ...) # uses #instance_method > mock_sheep.responds_like_instance(ninja, ...) # uses #respond_to? > mock_sheep.responds_like(cheese) # depends if cheese isclass/module I like this. One thing I don''t think I explained about my equivalent of "responds_like_type" i.e. "responds_like(instance_of(Sheep))" was that it would use a sneaky trick to construct an instance of the class without calling initialize purely in order to be able to use "respond_to?" instead of "instance_method"... def instance_of(klass) class_without_initialize = Class.new(klass) do def initialize(*args) # intentionally left blank end end class_without_initialize.new end I think I''d prefer to use "respond_to?" in this way in the underlying implementation of "responds_like_type". As I''ve said the "respond_to?" method feels like the correct method to use in all circumstances.> The responds_like_type version could even check if type implements > method_missing.As I''ve mentioned before, I think that "respond_to?" implementations are supposed to match up with "method_missing" implementations. So as long as we use "respond_to?" in the underlying implementation, I don''t think we need to check for the existence of "method_missing". Does anybody know if I''m talking rubbish about this convention/contract.> The failure mode here would be if you wanted to mock the /instance/ > methods of a /class/, using responds_like(type), but that seems very > much like an edge case, and you could explicitly use > responds_like_instance(type) anyway. > > Of course, all these checks would be done at method invocation time, so > late binding would work.Agreed. -- James. http://blog.floehopper.org
James Mead wrote:> Wow - it''s great to have so much input on this...It''s obviously something we''ve all been thinking about :)> I really like "quacks_like". :-) >So that''s three for three... hmm!> I''m not clear why you would want to be able to do this if Sheep & Ninja are > distinct classes.Think of a class/module definition describing a role (an aspect of behaviour). Say in your production code, you have a Shop object providing a bunch of services. You might want to use it as a RetailOpportunity, a DeliveryDestination, a ShopliftingVenue or any number of other expressively-named roles. These might well be implemented as mixins - the shop would "quack like a" delivery destination if you were a courier - and you want to test that behaviour. So you need a way of saying "I want to mock this module" (or more accurately "I want to mock something in the role described by this module"). Now either a) mocha takes the module/class name, and needs to "construct" an instance of the module, say using Object.new.extend(mod) or class constructor, or the user has to provide an object that includes the module, which pushes the onus onto the user (and by implication adds noise in the test). In the former case, you get the examples I suggested, and mocha has to do more work. In the latter case, mocha would take a ready-made instance, which means the user - and therefore the test - would have to do more stuff to express its intent.> If the Sheep class includes Enumerable at class > definition time, any Sheep instance will respond_to? Enumerable methods. >Right, but what if I don''t care about whether or not it''s a Sheep? Just that right now it''s being Enumerable?> However, it would be nice to have a solution that covers extending a Sheep > instance at runtime. I''m not so sure that there is anything wrong with your > "yuk" example given that it is a bit of an edge case. >I respectfully disagree. If you are using mocking to express interactions between roles, then this will be very much the norm.> One thing I don''t think I explained about my equivalent of > "responds_like_type" i.e. "responds_like(instance_of(Sheep))" was that it > would use a sneaky trick to construct an instance of the class without > calling initialize purely in order to be able to use "respond_to?" instead > of "instance_method"... >That''d work (unless the class defines methods or extends itself in its constructor, in which case all bets are off anyway!). In statically-typed languages the convention is to mock roles rather than classes (hence the focus on interfaces as roles). In a dynamic language things are a bit squishier. Mocking modules (with Object.new.extend(mod) and classes (by bypassing the constructor) seems like a pretty solid approach, and is more expressive than just mock(). My original use case was for the mock to fail with an undefined method error if I called a method that didn''t exist in the mocked type, and this will give me that and more besides, so I''m happy :)> I think I''d prefer to use "respond_to?" in this way in the underlying > implementation of "responds_like_type". As I''ve said the "respond_to?" > method feels like the correct method to use in all circumstances. >I agree that a well-implemented object should respond_to? the right things. Anyway, thanks for listening. Mocha is looking great James, and I''ll happily fall in line with whatever you decide. Cheers, Dan
Thanks for all the suggestions. I''ve just committed (revision 137) an implementation of "Mock#responds_like". As a first step, the "responds_like" method simply accepts an object which should "respond_to?" methods the way you want your mock to respond. It''d be cool if people could start using it and give us feedback on real scenarios where the shortcuts/refinements we talked about would be useful. We''ll also have a go at introducing it into our projects at Reevoo and see what we think. -- James. http://blog.floehopper.org