#70 ✓ resolved
James Mead

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

    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

    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

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

    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

    James Mead November 18th, 2010 @ 06:04 PM

    In fact the important change is here

  • James Mead
  • James Mead

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

  • James Mead

    James Mead November 25th, 2010 @ 03:29 PM

    • State changed from “open” to “resolved”
  • James Mead

    James Mead January 27th, 2011 @ 09:19 AM

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

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

New-ticket Create new ticket

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.

* GitHub repository
* Documentation
* Mailing List

People watching this ticket

Referenced by

Pages