btakita / rr

RR (Double Ruby) is a test double framework that features a rich selection of double techniques and a terse syntax.
http://github.com/rr/rr
MIT License
501 stars 58 forks source link

any_instance_of stubs failing with class hierarchies #70

Closed etiennebarrie closed 10 years ago

etiennebarrie commented 13 years ago

We’re seeing issues with the way any_instance_of stubs are reset.

These stubs apparently work by using on method_missing on the class, but the code (RR::Injections::MethodMissingInjection#reset line 44 called from RR::Space#reset_method_missing_injection) seems to be trying to remove method_missing even when it’s not defined on the subject class, but only on its parent class. RR::Injections::Injection#subject_has_method_defined? checks for all methods, even inherited ones, which then makes it fail when trying to remove the method.

Included is a failing spec showing the issue as a user of the lib. We also have a workaround that first orders the injections by @subject_class.ancestors.size before resetting them, which ensures that the parent classes’ method_missings are removed before those in inherited classes but it’s super ugly, so I’d rather not push that.

mcmire commented 11 years ago

This is what I'm getting using my fork:

$ ruby -I ~/code/github/forks/rr/lib -S rspec -b /tmp/rr-test.rb

with a class hierarchy
  stubs methods (FAILED - 1)

Failures:

  1) with a class hierarchy stubs methods
     Failure/Error: subject.to_s.should_not == "Subject is stubbed"
     NoMethodError:
       undefined method `dispatch_method' for nil:NilClass
     # /tmp/rr-test.rb:32:in `block (2 levels) in <top (required)>'
     # /Users/elliot/.rbenv/versions/1.9.3-p194-falcon/lib/ruby/gems/1.9.1/gems/rspec-core-2.12.2/lib/rspec/core/example.rb:114:in `instance_eval'
     # /Users/elliot/.rbenv/versions/1.9.3-p194-falcon/lib/ruby/gems/1.9.1/gems/rspec-core-2.12.2/lib/rspec/core/example.rb:114:in `block in run'
     # /Users/elliot/.rbenv/versions/1.9.3-p194-falcon/lib/ruby/gems/1.9.1/gems/rspec-core-2.12.2/lib/rspec/core/example.rb:254:in `with_around_each_hooks'
     # /Users/elliot/.rbenv/versions/1.9.3-p194-falcon/lib/ruby/gems/1.9.1/gems/rspec-core-2.12.2/lib/rspec/core/example.rb:111:in `run'
     # /Users/elliot/.rbenv/versions/1.9.3-p194-falcon/lib/ruby/gems/1.9.1/gems/rspec-core-2.12.2/lib/rspec/core/example_group.rb:388:in `block in run_examples'
     # /Users/elliot/.rbenv/versions/1.9.3-p194-falcon/lib/ruby/gems/1.9.1/gems/rspec-core-2.12.2/lib/rspec/core/example_group.rb:384:in `map'
     # /Users/elliot/.rbenv/versions/1.9.3-p194-falcon/lib/ruby/gems/1.9.1/gems/rspec-core-2.12.2/lib/rspec/core/example_group.rb:384:in `run_examples'
     # /Users/elliot/.rbenv/versions/1.9.3-p194-falcon/lib/ruby/gems/1.9.1/gems/rspec-core-2.12.2/lib/rspec/core/example_group.rb:369:in `run'
     # /Users/elliot/.rbenv/versions/1.9.3-p194-falcon/lib/ruby/gems/1.9.1/gems/rspec-core-2.12.2/lib/rspec/core/command_line.rb:28:in `block (2 levels) in run'
     # /Users/elliot/.rbenv/versions/1.9.3-p194-falcon/lib/ruby/gems/1.9.1/gems/rspec-core-2.12.2/lib/rspec/core/command_line.rb:28:in `map'
     # /Users/elliot/.rbenv/versions/1.9.3-p194-falcon/lib/ruby/gems/1.9.1/gems/rspec-core-2.12.2/lib/rspec/core/command_line.rb:28:in `block in run'
     # /Users/elliot/.rbenv/versions/1.9.3-p194-falcon/lib/ruby/gems/1.9.1/gems/rspec-core-2.12.2/lib/rspec/core/reporter.rb:34:in `report'
     # /Users/elliot/.rbenv/versions/1.9.3-p194-falcon/lib/ruby/gems/1.9.1/gems/rspec-core-2.12.2/lib/rspec/core/command_line.rb:25:in `run'
     # /Users/elliot/.rbenv/versions/1.9.3-p194-falcon/lib/ruby/gems/1.9.1/gems/rspec-core-2.12.2/lib/rspec/core/runner.rb:80:in `run'
     # /Users/elliot/.rbenv/versions/1.9.3-p194-falcon/lib/ruby/gems/1.9.1/gems/rspec-core-2.12.2/lib/rspec/core/runner.rb:17:in `block in autorun'

Finished in 0.00229 seconds
1 example, 1 failure

Failed examples:

rspec /tmp/rr-test.rb:20 # with a class hierarchy stubs methods

using this test file:

require 'rr'

require 'rspec/core'
require 'rspec/expectations'
RSpec.configure do |c|
  c.mock_with :nothing
end

class ParentClass; end
class SubjectClass < ParentClass; end

describe "with a class hierarchy" do
  include RR::Adapters::RSpec2

  before :each do
    any_instance_of(ParentClass, :to_s => "Subject is stubbed")
    any_instance_of(ParentClass, :foobar => lambda {:baz})
  end

  it "stubs methods" do
    any_instance_of(SubjectClass, :to_s => "Subject is stubbed")

    subject = SubjectClass.new
    subject.should_not respond_to(:baz)

    ParentClass.new.to_s.should == "Subject is stubbed"
    subject.to_s.should == "Subject is stubbed"
    subject.foobar.should == :baz

    RR.reset

    subject.to_s.should_not == "Subject is stubbed"
    subject.should_not respond_to(:baz)
  end
end