undefined method `mock' for some controller specs that expect a mocked model should receive a message #218

Closed
modellurgist opened this Issue Sep 23, 2010 · 4 comments

2 participants

@modellurgist

To replicate:

  • use ree-1.8.7-2010.02 (via RVM with a gemset, but not sure if that affects result)
  • use rails 3.0.0, rspec-rails 2.0.0.beta.22
  • create a new rails project and cd into its directory
  • $ rails g rspec:install
  • $ rails g scaffold Project (where Project is arbitrary model name)
  • create and migrate the database
  • $ rails g scaffold Project # an arbitrary model name
  • copy the spec for ProjectController's #create method to the specs for its #new method
  • modify that spec example to call 'get :new' but otherwise leave it the same
    
    it "saves the project" do
      Project.stub(:new).with({'these' => 'params'}) { mock_project(:save => true) }
      mock_project.should_receive(:save)
      get :new
    end 
    
  • in the Project controller's #new method add a line to call save on @project (this step is not necessary to reproduce the error)
  • execute $ rake spec
  • instead of a failure (on just the #new spec we added), it gives this error:
    
    Failure/Error: @mock_project ||= mock_model(Project, stubs).as_null_object
     undefined method mock' for #<RSpec::Core::ExampleGroup::Nested_2::Nested_1::Nested_2:0x103dcc9a0>
     # /Users/myuser/.rvm/gems/ree-1.8.7-2010.02@mygemset/gems/actionpack-3.0.0/lib/action_dispatch/testing/assertions/routing.rb:177:inmethod_missing'
     # /Users/myuser/.rvm/gems/ree-1.8.7-2010.02@mygemset/gems/rspec-rails-2.0.0.beta.22/lib/rspec/rails/mocks.rb:71:in mock_model'
     # ./spec/controllers/projects_controller_spec.rb:6:inmock_project'
     # ./spec/controllers/projects_controller_spec.rb:37
    
@dchelimsky
RSpec member

What happens if you change the spec to this:

it "saves the project" do
    mock_project.should_receive(:save) {true}
    Project.stub(:new).with({'these' => 'params'}) { mock_project }
    get :new
  end
@dchelimsky
RSpec member

The problem is that the mock_project method only adds the stubs the first time it is invoked. The first line in the example uses the block format to spec the return value:

Project.stub(:new).with({'these' => 'params'}) { mock_project(:save => true) }

That block gets evaluated when Project receives new, which is triggered by the third line of the example:

get :new

This means that the first invocation of mock_project happens on the 2nd line of the example:

mock_project.should_receive(:save)

Therefore the stub of save is never set on the mock project.

I'm going to change the generated mock_project to read like this:

def mock_project(stubs={})
  @mock_project ||= mock_model(Project).as_null_object
  @mock_project.stub(stubs)
end

That will make it so the stubs are additive (i.e. each invocation of mock_project adds the new stubs. If you do that now, this should work as you expect.

@dchelimsky
RSpec member

Generated mock_[model] method adds (optional) stubs on each reference.

  • This fixes an edge case bug that appears when defining stubs using the lazy-eval'd block form (obj.stub(:m) {v}).
  • Closed by ae50bf9.
@dchelimsky
RSpec member

FYI - the method should be this (not what I posted two comments ago):

  def mock_project(stubs={})
    (@mock_project ||= mock_model(Gadget).as_null_object).tap do |project|
      project.stub(stubs) unless stubs.empty?
    end
  end
@amatsuda amatsuda pushed a commit that referenced this issue Nov 15, 2011
@dchelimsky dchelimsky Generated mock_[model] method adds (optional) stubs on each reference.
- This fixes an edge case bug that appears when defining stubs using the
  lazy-eval'd block form (obj.stub(:m) {v}).
- Closes #218.
ae50bf9
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment