Mock#to_ary and Array#flatten problem
Reported by James Mead | October 22nd, 2010 @ 06:18 PM
From a message  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
it seems related to patch
#22630 on Rubyforge.
Can you reproduce it?
Comments and changes to this ticket
- 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.
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 >
I've added some RubySpec tests to better define Array#flatten behaviour :-
We've taken a bit of a step back from the specific issue you've raised. We've decided that a
Mockinstance should be more of a
BlankSlateand not inherit any of
Objects'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#flattendoes not arise in Ruby 1.8 is that
Object#to_a. In Ruby 1.9 the issue does not arise, because
Object#to_adoes 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 :-
object = stub("object").quacks_like(Object.new)
(b) explicitly set up a stub for
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
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>