Lighthouse has a new layout. Prefer the old one? Return to the old layout, and switch back any time from the link at the top of each page.

Mock#to_ary and Array#flatten problem

#70

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...

Reported by James Mead · October 22nd, 2010 @ 06:18 PM

State: resolved
Milestone: none
Assigned to: James Mead James Mead

Activity

  1. James Mead
    James Mead
    • 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.

    October 23rd, 2010 @ 02:20 PM

  2. Robert Pankowecki
    Robert Pankowecki

    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 >
    

    November 14th, 2010 @ 06:03 PM

  3. James Mead
    James Mead

    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.

    November 14th, 2010 @ 07:05 PM

  4. James Mead
  5. James Mead
    James Mead

    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!

    November 18th, 2010 @ 06:03 PM

  6. James Mead
    James Mead

    In fact the important change is here

    November 18th, 2010 @ 06:04 PM

  7. James Mead
  8. James Mead
    James Mead

    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 a BlankSlate and 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#flatten does not arise in Ruby 1.8 is that Mock does inherit Object#to_a. In Ruby 1.9 the issue does not arise, because Object#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 or to_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.

    November 25th, 2010 @ 03:28 PM

  9. James Mead
    James Mead
    • State changed from open to resolved

    November 25th, 2010 @ 03:29 PM

  10. James Mead
    James Mead

    For reference RubySpec have introduced some extra tests for Array#flatten based on my patches.

    January 27th, 2011 @ 09:19 AM

  11. Robert Pankowecki

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.