First off, I love mocha and have been using it all over the place ever since I found it a few months ago. So I noticed the other day rather belatedly that mocha-0.4.0 had been released and that we can now do the object.stubs(:method).returns (:first_value, :second_value). Much neater than fiddling with lambdas everytime this sort of behaviour is needed But can we go further ? I was writing a test today and I wanted the first call to a method to raise an exception and the next call to return a value (I was testing a method on an ActiveRecord object where if save! raises StaleObjectError then we reload and retry in the proper way, the second call to save! shouldn''t raise.). The same basic method works: lambdas = [lambda {raise ''Foo''}, lambda {return true}] object.stubs(:save!).returns {lambda {lambdas.shift.call}} It''s a lot less readable than your average lovely bit of mocha based test. Can anyone think of a way of doing this that looks nice? Fred
On 03/03/07, Frederick Cheung <fred at 82ask.com> wrote:> > First off, I love mocha and have been using it all over the place > ever since I found it a few months ago.Good stuff. So I noticed the other day rather belatedly that mocha-0.4.0 had been> released and that we can now do the object.stubs(:method).returns > (:first_value, :second_value). Much neater than fiddling with lambdas > everytime this sort of behaviour is needed > > But can we go further ? I was writing a test today and I wanted the > first call to a method to raise an exception and the next call to > return a value (I was testing a method on an ActiveRecord object > where if save! raises StaleObjectError then we reload and retry in > the proper way, the second call to save! shouldn''t raise.). > > The same basic method works: > > lambdas = [lambda {raise ''Foo''}, lambda {return true}] > object.stubs(:save!).returns {lambda {lambdas.shift.call}} > > It''s a lot less readable than your average lovely bit of mocha based > test. > > Can anyone think of a way of doing this that looks nice? >Interesting. Perhaps we can at least do this... object.stubs(:save!).returns(lambda { raise ''Foo'' }, true ) Or (if we add a ''raises'' method to TestCase)... object.stubs(:save!).returns(raises(), true) Thoughts anyone? -- James. http://blog.floehopper.org
On 5 Mar 2007, at 11:06, James Mead wrote:>> The same basic method works: >> >> lambdas = [lambda {raise ''Foo''}, lambda {return true}] >> object.stubs(:save!).returns {lambda {lambdas.shift.call}} >> >> It''s a lot less readable than your average lovely bit of mocha based >> test. >> >> Can anyone think of a way of doing this that looks nice? >> > > Interesting. Perhaps we can at least do this... > > object.stubs(:save!).returns(lambda { raise ''Foo'' }, true ) > > Or (if we add a ''raises'' method to TestCase)... > > object.stubs(:save!).returns(raises(), true)Someone a little while back proposed added a then() method. It was a little more cumbersome than returns(:foo_1, :foo_2) but object.stubs(:save!).raises(''Foo'').then.returns(true) isn''t too bad. Fred
How about saying that if what is passed to returns() is an exception, or a class that''s a descendant of Exception, it''s raised instead of returned? You could still return an exception by returning it from a lambda. obj.stubs(:save!).returns(RuntimeError.new("testing..."), true) # Raises, true obj.stubs(:save!).returns(RuntimeError, true) # Raises, true obj.stubs(:save!).returns(lambda {RuntimeError}, true) # returns the exception object, true Adding raises() to TestObject seems a bit intrusive. - James Moore
On 05/03/07, James Moore <jamesthepiper at gmail.com> wrote:> > How about saying that if what is passed to returns() is an exception, or a > class that''s a descendant of Exception, it''s raised instead of returned? > You could still return an exception by returning it from a lambda. > > obj.stubs(:save!).returns(RuntimeError.new("testing..."), true) # Raises, > true > obj.stubs(:save!).returns(RuntimeError, true) # Raises, true > obj.stubs(:save!).returns(lambda {RuntimeError}, true) # returns the > exception object, trueI''m not sure it''s so clear what''s going to happen in the above cases. Adding raises() to TestObject seems a bit intrusive.>In what way do you think it would be intrusive? -- James. http://blog.floehopper.org
I have to agree with James - I''m not a fan of returns changing its behaviour based on the return type. What if I''m writing an exceptions factory, or some sort of exception processor (say something to log parts of a stack trace)? I''m happy with .returns(quietly) and .raises(mayhem). Cheers, Dan James Mead wrote:> On 05/03/07, James Moore <jamesthepiper at gmail.com> wrote: > >> How about saying that if what is passed to returns() is an exception, or a >> class that''s a descendant of Exception, it''s raised instead of returned? >> You could still return an exception by returning it from a lambda. >> >> obj.stubs(:save!).returns(RuntimeError.new("testing..."), true) # Raises, >> true >> obj.stubs(:save!).returns(RuntimeError, true) # Raises, true >> obj.stubs(:save!).returns(lambda {RuntimeError}, true) # returns the >> exception object, true >> > > > I''m not sure it''s so clear what''s going to happen in the above cases. > > Adding raises() to TestObject seems a bit intrusive. > > > In what way do you think it would be intrusive? > >
On 3/9/07, Dan North <dan at tastapod.com> wrote:> > I have to agree with James - I''m not a fan of returns changing its > behaviour based on the return type. What if I''m writing an exceptions > factory, or some sort of exception processor (say something to log parts > of a stack trace)? > > I''m happy with .returns(quietly) and .raises(mayhem). >It does already, though. For example, there''s not a clean way to mock something that returns closures, which is probably more common than something that returns exceptions. If returns() is given something that happens to be a lambda, it doesn''t return the lambda (which is what you''d normally expect), it calls it and returns its value instead. I''m just suggesting adding to that concept: case return_value when its a Proc: call the proc, return the result # Existing behavior when its an Exception, or an instance of an exception, raise it # Additional behavior else return return_value end I do see your point though - I''m not convinced this is worth arguing strongly for, but I do think it fits well with the current code. - James Moore
Ah, that''s "surprising". I haven''t tried using lambdas with mocha, but that''s not what I would expect. I expect returns to, um, return stuff. I would expect to use raises and executes (or maybe invokes) for exceptions and lambdas respectively. returns(&block) actually executing the block seems counter-intuitive to me. sheep.expects(:eat).with(grass).*executes*(proc {puts "chewing"}) proc_factory.expects(:create).*returns*(proc { puts "pretend the factory created this" }) Come to think of it, is there any way in mocha of doing the second one? Cheers, Dan James Moore wrote:> On 3/9/07, Dan North <dan at tastapod.com> wrote: > >> I have to agree with James - I''m not a fan of returns changing its >> behaviour based on the return type. What if I''m writing an exceptions >> factory, or some sort of exception processor (say something to log parts >> of a stack trace)? >> >> I''m happy with .returns(quietly) and .raises(mayhem). >> >> > > It does already, though. For example, there''s not a clean way to mock > something that returns closures, which is probably more common than > something that returns exceptions. If returns() is given something that > happens to be a lambda, it doesn''t return the lambda (which is what you''d > normally expect), it calls it and returns its value instead. I''m just > suggesting adding to that concept: > > case return_value > when its a Proc: call the proc, return the result # Existing behavior > when its an Exception, or an instance of an exception, raise it # Additional > behavior > else return return_value > end > > I do see your point though - I''m not convinced this is worth arguing > strongly for, but I do think it fits well with the current code. > > - James Moore > _______________________________________________ > mocha-developer mailing list > mocha-developer at rubyforge.org > http://rubyforge.org/mailman/listinfo/mocha-developer >
On 3/9/07, James Mead <jamesmead44 at gmail.com> wrote:> > Adding raises() to TestObject seems a bit intrusive. > > > > In what way do you think it would be intrusive? > > -- > James. > http://blog.floehopper.org >It''s adding a method to another, basically unrelated, class object just to change the readability of mocks. It seems goofy to add a method to a class where the new method has little or no interaction with the class it belongs to. - James Moore
On 10/03/07, Dan North <dan at tastapod.com> wrote:> > Ah, that''s "surprising". I haven''t tried using lambdas with mocha, but > that''s not what I would expect. I expect returns to, um, return stuff. I > would expect to use raises and executes (or maybe invokes) for > exceptions and lambdas respectively. > > returns(&block) actually executing the block seems counter-intuitive to > me. > > sheep.expects(:eat).with(grass).*executes*(proc {puts "chewing"}) > > proc_factory.expects(:create).*returns*(proc { puts "pretend the factory > created this" }) > > Come to think of it, is there any way in mocha of doing the second one? > > Cheers, > Dan > > James Moore wrote: > > On 3/9/07, Dan North <dan at tastapod.com> wrote: > > > >> I have to agree with James - I''m not a fan of returns changing its > >> behaviour based on the return type. What if I''m writing an exceptions > >> factory, or some sort of exception processor (say something to log > parts > >> of a stack trace)? > >> > >> I''m happy with .returns(quietly) and .raises(mayhem). > >> > >> > > > > It does already, though. For example, there''s not a clean way to mock > > something that returns closures, which is probably more common than > > something that returns exceptions. If returns() is given something that > > happens to be a lambda, it doesn''t return the lambda (which is what > you''d > > normally expect), it calls it and returns its value instead. I''m just > > suggesting adding to that concept: > > > > case return_value > > when its a Proc: call the proc, return the result # Existing behavior > > when its an Exception, or an instance of an exception, raise it # > Additional > > behavior > > else return return_value > > end > > > > I do see your point though - I''m not convinced this is worth arguing > > strongly for, but I do think it fits well with the current code. > > > > - James Moore >James & Dan - you both make very valid points. Thanks. I''d like to make the API less surprising. Surprising is BAD! Specifically I''d like to make it possible to return exactly what is specified in the ''returns'' method, even if that is a Proc instance. My colleague Chris pointed out that there is a similar issue in the ''with'' method which can accept a block. I''d like to fix that too. My colleagues and I have been chucking a few ideas around. I''ll try and post about these ideas as soon as I have some time. One of the issues is providing a simple migration path for those with lots of tests relying on the current behaviour. I''d be interested whether you have any thoughts. -- James. http://blog.floehopper.org
On 12/03/07, James Moore <jamesthepiper at gmail.com> wrote:> > On 3/9/07, James Mead <jamesmead44 at gmail.com> wrote: > > > > Adding raises() to TestObject seems a bit intrusive. > > > > > > > In what way do you think it would be intrusive? > > It''s adding a method to another, basically unrelated, class object just to > change the readability of mocks. It seems goofy to add a method to a > class > where the new method has little or no interaction with the class it > belongs > to. > > - James Moore >I agree it''s not very object-oriented, but I''d be happy to sacrifice this for readability. Readability is very important to me. Thanks for responding - it helps to know why you think it would be intrusive. -- James. http://blog.floehopper.org
On 3/12/07, James Mead <jamesmead44 at gmail.com> wrote:> > I agree it''s not very object-oriented, but I''d be happy to sacrifice this > for readability. Readability is very important to me. > > Thanks for responding - it helps to know why you think it would be > intrusive.How about this: it''s not predictable. I think what you''re saying is that you''d like to add Test::Unit::TestCase#raises, but that only works when you''re creating stuff inside a test method. Does that mean creating return values outside of a test is going to look very different than creating them inside a test? It also seems vulnerable to namespace clashes - seems like if you have a good argument for adding Test::Unit::TestCase#raises, so can someone else, for some other test-related library, and they''re not going to play well together. (I''m not wildly opposed to this, it''s just feeling like there''s a better way somewhere.) - James Moore