rspec / rspec-mocks

RSpec's 'test double' framework, with support for stubbing and mocking
https://rspec.info
MIT License
1.16k stars 358 forks source link

allow no longer works with ActiveRecord classes #972

Closed tisba closed 9 years ago

tisba commented 9 years ago

While updating a rails 4.2.1 project from rspec 3.2.0 to 3.3.0 I got a lot of failing specs.

I noticed that the issue can be reproduced simply by the following (pointless spec):

it "will fail" do
  allow(User).to receive(:confirmed).and_return(:something)
end
myronmarston commented 9 years ago

Thanks for reporting this.

Can you provide a backtrace for the error you are getting? We haven't seen that problem and can't guess what the problem is. Also, if you're able to provide a reproducible example, that would help us get to the bottom of this quickly.

Thanks!

baburdick commented 9 years ago

+1

    context "error occurred" do
      before do
        allow(Account).to receive(:new).and_raise("Oops")
      end

      it "returns 'error'" do
        post :create, params

        expect(response                ).to be_ok
        expect(json_response["status"] ).to eq "error"
        expect(json_response["message"]).to eq "Oops"
      end
    end
Failures:

  1) AccountsController#create error occurred returns 'error'
     Failure/Error: allow(Account).to receive(:new).and_raise("Oops")
     ActiveRecord::ActiveRecordError:
       ActiveRecord::Base doesn't belong in a hierarchy descending from ActiveRecord
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/activerecord-4.1.10/lib/active_record/inheritance.rb:71:in `base_class'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/activerecord-4.1.10/lib/active_record/attribute_methods.rb:78:in `block in define_attribute_methods'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/activerecord-4.1.10/lib/active_record/attribute_methods.rb:76:in `define_attribute_methods'
     # ./spec/controllers/accounts_controller_spec.rb:3:in `block (4 levels) in <top (required)>'
myronmarston commented 9 years ago

Thanks, @baburdick, that's helpful. Can you re-run it with -b to enable the full backtrace so we can see the RSpec stack frames?

baburdick commented 9 years ago
Failures:

  1) CustomerAccountsController#create error occurred returns 'error'
     Failure/Error: allow(AccountCore::CustomerAccount).to receive(:new).and_raise("Oops")
     ActiveRecord::ActiveRecordError:
       ActiveRecord::Base doesn't belong in a hierarchy descending from ActiveRecord
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/activerecord-4.1.10/lib/active_record/inheritance.rb:71:in `base_class'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/activerecord-4.1.10/lib/active_record/attribute_methods.rb:78:in `block in define_attribute_methods'
     # /Users/bruceb/.rvm/rubies/ruby-2.1.2/lib/ruby/2.1.0/mutex_m.rb:73:in `synchronize'
     # /Users/bruceb/.rvm/rubies/ruby-2.1.2/lib/ruby/2.1.0/mutex_m.rb:73:in `mu_synchronize'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/activerecord-4.1.10/lib/active_record/attribute_methods.rb:76:in `define_attribute_methods'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-rails-3.3.0/lib/rspec/rails/active_record.rb:12:in `block (2 levels) in initialize_activerecord_configuration'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-mocks-3.3.0/lib/rspec/mocks/verifying_proxy.rb:103:in `call'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-mocks-3.3.0/lib/rspec/mocks/verifying_proxy.rb:103:in `block in initialize'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-mocks-3.3.0/lib/rspec/mocks/verifying_proxy.rb:102:in `each'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-mocks-3.3.0/lib/rspec/mocks/verifying_proxy.rb:102:in `initialize'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-mocks-3.3.0/lib/rspec/mocks/proxy.rb:345:in `initialize'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-mocks-3.3.0/lib/rspec/mocks/space.rb:154:in `new'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-mocks-3.3.0/lib/rspec/mocks/space.rb:154:in `proxy_not_found_for'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-mocks-3.3.0/lib/rspec/mocks/space.rb:108:in `block (2 levels) in proxy_for'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-mocks-3.3.0/lib/rspec/mocks/space.rb:108:in `fetch'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-mocks-3.3.0/lib/rspec/mocks/space.rb:108:in `block in proxy_for'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-mocks-3.3.0/lib/rspec/mocks/space.rb:106:in `synchronize'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-mocks-3.3.0/lib/rspec/mocks/space.rb:106:in `proxy_for'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-mocks-3.3.0/lib/rspec/mocks/proxy.rb:394:in `superclass_proxy'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-mocks-3.3.0/lib/rspec/mocks/proxy.rb:369:in `original_method_handle_for'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-mocks-3.3.0/lib/rspec/mocks/method_double.rb:37:in `original_method'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-mocks-3.3.0/lib/rspec/mocks/method_double.rb:26:in `original_implementation_callable'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-mocks-3.3.0/lib/rspec/mocks/verifying_proxy.rb:166:in `initialize'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-mocks-3.3.0/lib/rspec/mocks/verifying_proxy.rb:182:in `new'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-mocks-3.3.0/lib/rspec/mocks/verifying_proxy.rb:182:in `for'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-mocks-3.3.0/lib/rspec/mocks/verifying_proxy.rb:99:in `block in initialize'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-mocks-3.3.0/lib/rspec/mocks/verifying_proxy.rb:24:in `yield'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-mocks-3.3.0/lib/rspec/mocks/verifying_proxy.rb:24:in `ensure_implemented'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-mocks-3.3.0/lib/rspec/mocks/verifying_proxy.rb:9:in `add_stub'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-mocks-3.3.0/lib/rspec/mocks/matchers/receive.rb:96:in `setup_method_substitute'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-mocks-3.3.0/lib/rspec/mocks/matchers/receive.rb:84:in `setup_mock_proxy_method_substitute'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-mocks-3.3.0/lib/rspec/mocks/matchers/receive.rb:41:in `setup_allowance'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-mocks-3.3.0/lib/rspec/mocks/targets.rb:45:in `define_matcher'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-mocks-3.3.0/lib/rspec/mocks/targets.rb:14:in `block in delegate_to'
     # ./spec/controllers/accounts_controller_spec.rb:3:in `block (4 levels) in <top (required)>'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:378:in `instance_exec'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:378:in `instance_exec'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/hooks.rb:357:in `run'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/hooks.rb:521:in `block in run_owned_hooks_for'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/hooks.rb:520:in `each'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/hooks.rb:520:in `run_owned_hooks_for'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/hooks.rb:607:in `block in run_example_hooks_for'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/hooks.rb:606:in `reverse_each'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/hooks.rb:606:in `run_example_hooks_for'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/hooks.rb:476:in `run'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:424:in `run_before_example'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:205:in `block in run'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:430:in `block in with_around_and_singleton_context_hooks'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:388:in `block in with_around_example_hooks'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/hooks.rb:478:in `block in run'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/hooks.rb:618:in `block in run_around_example_hooks_for'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:273:in `call'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:273:in `call'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-rails-3.3.0/lib/rspec/rails/example/controller_example_group.rb:190:in `block (2 levels) in <module:ControllerExampleGroup>'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:378:in `instance_exec'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:378:in `instance_exec'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/hooks.rb:389:in `execute_with'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/hooks.rb:620:in `block (2 levels) in run_around_example_hooks_for'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:273:in `call'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:273:in `call'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-rails-3.3.0/lib/rspec/rails/adapters.rb:127:in `block (2 levels) in <module:MinitestLifecycleAdapter>'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:378:in `instance_exec'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:378:in `instance_exec'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/hooks.rb:389:in `execute_with'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/hooks.rb:620:in `block (2 levels) in run_around_example_hooks_for'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:273:in `call'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:273:in `call'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/hooks.rb:621:in `run_around_example_hooks_for'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/hooks.rb:478:in `run'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:388:in `with_around_example_hooks'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:430:in `with_around_and_singleton_context_hooks'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:203:in `run'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:559:in `block in run_examples'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:555:in `map'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:555:in `run_examples'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:521:in `run'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:522:in `block in run'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:522:in `map'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:522:in `run'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:522:in `block in run'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:522:in `map'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:522:in `run'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/runner.rb:115:in `block (3 levels) in run_specs'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/runner.rb:115:in `map'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/runner.rb:115:in `block (2 levels) in run_specs'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/configuration.rb:1627:in `with_suite_hooks'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/runner.rb:114:in `block in run_specs'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/reporter.rb:77:in `report'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/runner.rb:113:in `run_specs'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/runner.rb:89:in `run'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/runner.rb:73:in `run'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/lib/rspec/core/runner.rb:41:in `invoke'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/gems/rspec-core-3.3.0/exe/rspec:4:in `<top (required)>'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/bin/rspec:23:in `load'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/bin/rspec:23:in `<main>'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/bin/ruby_executable_hooks:15:in `eval'
     # /Users/bruceb/.rvm/gems/ruby-2.1.2@account_service/bin/ruby_executable_hooks:15:in `<main>'

Finished in 0.03399 seconds (files took 3.25 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/controllers/accounts_controller_spec.rb:6 # AccountsController#create error occurred returns 'error'
cupakromer commented 9 years ago

I've reproduced with RSpec 3.3.0 on both [Ruby 2.2.2 with Rails 4.2.1] and [Ruby 2.1.5 with Rails 4.1.10]. Here were my basic steps:

$ rails new issue972 --skip-test-unit --skip-bundle
$ cd issue972
# Edit Gemfile adding rspec-rails
$ bundle
$ bundle binstubs rspec-core
$ bin/rails g rspec:install
# Edit spec/spec_helper.rb removing =being and =end lines
$ bin/rails g model User name email
$ bin/rake db:create db:migrate
# Edit spec/models/user_spec.rb

The spec file looks like:

require 'rails_helper'

RSpec.describe User, type: :model do
  it "works with basic mocks" do
    allow(User).to receive(:new).and_raise("Oops")
    expect { User.new }.to raise_error "Oops"
  end
end

It's late so I'll have to look at it more later. I did verify that putting rspec-rails 3.2.3 in the Gemfile instead of 3.3.0 make the spec pass. I'm currently unsure if this is due to a change in rspec-rails or rspec-mocks.

myronmarston commented 9 years ago

I've also dug into this a bit. I've bisected rspec-mocks and it points to e5bfbcb75bca581924697ab0e9fd9447e9257b76 as the source commit for this problem. The problem is that User.define_attribute_methods works fine but ActiveRecord::Base.define_attribute_methods does not. It raises the error we are seeing. Due to changes in e5bfbcb75bca581924697ab0e9fd9447e9257b76, when you stub a class, the before_verifying_doubles callback used by rspec-rails is invoked once for each superclass. This happens because the hook is called from the initialize method of the verifying proxy, and the logic for class proxies instantiates the superclass proxies for certain operations. As a result, the rspec-rails callback is being called with ActiveRecord::Base and it's calling the method and blowing up.

973 has a failing spec demonstrating the problem. I'm not sure how to fix it.

We may also want to update the rspec-rails callback to not send define_attribute_methods to ActiveRecord::Base. That would fix this specific problem but I think it's a bug for the rspec-mocks callback to be called once for each superclass.

myronmarston commented 9 years ago

I've got a fix in rspec/rspec-rails#1395, although we are going to also want to fix the rspec-mocks callback so it doesn't get triggered for every superclass. But that can be a separate PR.

I'm hoping to release rspec-rails with a fix later in the weekend.

myronmarston commented 9 years ago

rspec-rails 3.3.1 has been released with a fix.

tisba commented 9 years ago

Wow, that's awesome! Thanks for the quick fix @myronmarston!

esambo commented 9 years ago

:+1:

baburdick commented 9 years ago

Thanks @myronmarston!