Mock#to_ary and Array#flatten problem
Reported by James Mead | October 22nd, 2010 @ 06:18 PM
From a message [1] on the mailing list from Robert Pankowecki :-
ruby-1.9.2-head > m = ActiveSupport::TestCase.new('a').mock # => #<Mock:0xae73b64>
ruby-1.9.2-head > [[m]].flatten # => Mocha::ExpectationError: unexpected invocation: #<Mock:0xae73b64>.to_ary()
from (irb):12:in `flatten'
from (irb):12
from /home/rupert/.rvm/gems/ruby-1.9.2-head/gems/railties-3.0.0/lib/rails/commands/console.rb:44:in `start'
from /home/rupert/.rvm/gems/ruby-1.9.2-head/gems
Looking at
http://mail-index.netbsd.org/pkgsrc-changes/2010/08/03/msg044420.html
it seems related to patch #22630
on Rubyforge.
Can you reproduce it?
[1] http://groups.google.com/group/mocha-developer/browse_thread/thread...
Comments and changes to this ticket
-
James Mead October 23rd, 2010 @ 02:20 PM
- State changed from new to open
- Tag changed from bug-report, ruby19 to bug-report, flatten, ruby19, to_a, to_ary
I can reproduce this problem in a slightly simpler way :-
$ ruby -v ruby 1.9.2p14 (2010-10-02 revision 29393) [x86_64-darwin10.4.0] $ irb ruby-1.9.2-head > require "rubygems"; require "mocha" => true ruby-1.9.2-head > [[Object.new]].flatten => [#<Object:0x000001009ca3a0>] ruby-1.9.2-head > [[Mocha::Mock.new]].flatten => Mocha::ExpectationError: unexpected invocation: #<Mock:0x1009f2990>.to_ary() ruby-1.9.2-head > [[Mocha::Mock.new.quacks_like(Object.new)]].flatten => [#<Mock:0x1009e1f00>] ruby-1.9.2-head > Object.new.to_ary => NoMethodError: undefined method `to_ary' for #<Object:0x000001009bbf58> ruby-1.9.2-head > Mocha::Mock.new.to_ary => Mocha::ExpectationError: unexpected invocation: #<Mock:0x1009c2498>.to_ary() ruby-1.9.2-head > Mocha::Mock.new.quacks_like(Object.new).to_ary => NoMethodError: undefined method `to_ary' for #<Mock:0x1009a23a0> which responds like #<Object:0x1009a22d8>
It seems as if the problem is that Array#flatten is relying on an object to raise a NoMethodError. It doesn't seem to be enough to return false to respond_to?. Explicityly telling the mock object to respond like a ruby object seems to solve the problem, because then it raises a NoMethodError instead of a Mocha::ExpectationError.
I think this is not a problem in 1.8.7, because, although it generates a warning, Object#to_a returns a single element array of itself. I think flatten will try calling Object#to_a as well as Object#to_ary, and since Mocha::Mock inherits from Object, Mocha::Mock#to_a will return a single element array of itself too.
$ ruby -v ruby 1.8.7 (2010-08-16 patchlevel 302) [i686-darwin10.4.0] $ irb ruby-1.8.7-p302 > require "rubygems"; require "mocha" => true ruby-1.8.7-p302 > [[Object.new]].flatten => [#<Object:0x101276a08>] ruby-1.8.7-p302 > [[Mocha::Mock.new]].flatten => [#<Mock:0x101272908>] ruby-1.8.7-p302 > Object.new.to_ary => NoMethodError: undefined method `to_ary' for #<Object:0x101269998> ruby-1.8.7-p302 > Mocha::Mock.new.to_ary => Mocha::ExpectationError: unexpected invocation: #<Mock:0x101266338>.to_ary() ruby-1.8.7-p302 > Object.new.to_a warning: default `to_a' will be obsolete => [#<Object:0x10125d418>] ruby-1.8.7-p302 > Mocha::Mock.new.to_a warning: default `to_a' will be obsolete => [#<Mock:0x10125a290>] ruby-1.8.7-p302 > Mocha::Mock.new.quacks_like(Object.new).to_a warning: default `to_a' will be obsolete => [#<Mock:0x101254c50>]
I'm going to have to have a bit of a think about whether there's a better solution to this. One option, would be to set Object.new as the responder by default, but I'm not sure how backwardly compatible that change would be. Another possibility would be to raise NoMethodError's instead of Mocha::ExpectationError, at least in the case where no method has been stubbed, but again I need to think about the implications.
In the end it may turn out that it's better to bump the major version and create a new version of Mocha that supports Ruby 1.9, but not Ruby 1.8. And keep the old version supporting Ruby 1.8.
-
Robert Pankowecki November 14th, 2010 @ 06:03 PM
I started the discussion about it http://www.ruby-forum.com/topic/450307#961367
I think that a nice and easy workaround would be to inherit Mocha::ExpectationError from NoMethodError
What do you think ?ruby-1.9.2-head > class MyErr < NoMethodError ruby-1.9.2-head ?> end => nil ruby-1.9.2-head > class N ruby-1.9.2-head ?> def method_missing(*params) ruby-1.9.2-head ?> puts params.inspect ruby-1.9.2-head ?> raise MyErr ruby-1.9.2-head ?> end ruby-1.9.2-head ?> end => nil ruby-1.9.2-head > N.new.to_ary [:to_ary] MyErr: MyErr from (irb):72:in `method_missing' from (irb):75 from /home/rupert/.rvm/rubies/ruby-1.9.2-head/bin/irb:17:in `<main>' ruby-1.9.2-head > [N.new].flatten [:to_ary] => [#<N:0x9417b30>] ruby-1.9.2-head >
-
James Mead November 14th, 2010 @ 07:05 PM
Thanks for starting the discussion on the Ruby mailing list - I think it would be useful to find out if this behaviour is intentional. It's worth noting this recent change to Mocha. I'll try and find some time to do some more thinking about the problem.
-
James Mead November 18th, 2010 @ 04:41 PM
I've added some RubySpec tests to better define Array#flatten behaviour :-
https://github.com/freerange/rubyspec/commit/7dbeac153443b12a6eb299...
https://github.com/freerange/rubyspec/commit/3d8edf7d24899a005e3cd4... -
James Mead November 18th, 2010 @ 06:03 PM
That seems to have elicited a response of sorts.
I can't seem to get to the ticket at the moment, but the change seems to have been made consciously in this commit.
So it looks like I've got some serious thinking to do!
-
James Mead November 25th, 2010 @ 03:28 PM
Hi Robert,
Sorry for the delay. I've just been doing some thinking about this and talking it through with my GoFreeRange colleague, Chris Roos.
We've taken a bit of a step back from the specific issue you've raised. We've decided that a
Mock
instance should be more of aBlankSlate
and not inherit any ofObjects
's instance methods. This is not the case at the moment. I've created another ticket to fix this, but it's going to take a bit of doing because it's a moderately significant change and will require some kind of deprecation warning and change in version number.As you know, the reason the issue with
Array#flatten
does not arise in Ruby 1.8 is thatMock
does inheritObject#to_a
. In Ruby 1.9 the issue does not arise, becauseObject#to_a
does not exist and is therefore not inherited by Mock.The upshot is that I think the behaviour in Ruby 1.8 is incorrect and the behaviour in Ruby 1.9 is correct.
So I'd recommend you solve your issue by either :-
(a) using
Mock#quacks_like(Object.new)
e.g.object = stub("object").quacks_like(Object.new)
or
(b) explicitly set up a stub for
to_a
orto_ary
, e.g.object = stub("object"); object.stubs(:to_ary).returns([object])
However, I want to thank you for raising this issue, because you've made me think more carefully about the desirable behaviour for a
Mock
instance.Cheers, James.
-
James Mead November 25th, 2010 @ 03:29 PM
- State changed from open to resolved
-
James Mead January 27th, 2011 @ 09:19 AM
For reference RubySpec have introduced some extra tests for Array#flatten based on my patches.
Please Sign in or create a free account to add a new ticket.
With your very own profile, you can contribute to projects, track your activity, watch tickets, receive and update tickets through your email and much more.
Create your profile
Help contribute to this project by taking a few moments to create your personal profile. Create your profile ยป
A mocking & stubbing library for Ruby.
* <a href="http://github.com/floehopper/mocha">GitHub repository</a>
* <a href="http://mocha.rubyforge.org">Documentation</a>
* <a href="http://groups.google.com/group/mocha-developer">Mailing List</a>
People watching this ticket
Tags
Referenced by
- 74 Mock objects should be more of a BlankSlate and no inherit Object instance methods by default See this ticket for some relevant thoughts on the matter.
- 84 Mocking 'each' method with yields/multiple_yields behaves incorrectly under JRuby Hmm. I wonder whether this is related to this other tick...