Rspec-rails: allow(instance).to receive(:method).and_return(value) does not stub instance method

Created on 29 Jan 2015  Â·  10Comments  Â·  Source: rspec/rspec-rails

Using Rspec 3.0 and Factory Girl, given a factory girl instance, 'girl' and a method 'meth', the method is not properly stubbed.

allow(girl).to receive(:meth).and_return(true)

The above method, meth, is still called. Using allow_any_instance_of(Class).to receive(:method).and_return(value) does work, but raises a deprecation warning (as it should).

Most helpful comment

I'm just going to guess this is either a controller, request, or feature spec. And that the instance you are stubbing is what you get back from either FactoryGirl's create or build method. However, you are then loading a new object in the controller via find, where, etc. which is not the same object.

All 10 comments

Odd. I've never seen a method stub to fail like that. It suggests that factory girl is doing something odd, which surprises me but I admittedly haven't used factory girl in years.

@jmstone617, do you think you could put together an reproducible example we can use for troubleshooting this? I believe factory girl no longer assumes ActiveRecord so you could theoretically create an isolated example that doesn't use rails at all.

I’ll get something put together by the end of the day tomorrow. Thanks!

—
Sent from Mailbox

On Thu, Jan 29, 2015 at 2:23 PM, Myron Marston [email protected]
wrote:

Odd. I've never seen a method stub to fail like that. It suggests that factory girl is doing something odd, which surprises me but I admittedly haven't used factory girl in years.

@jmstone617, do you think you could put together an reproducible example we can use for troubleshooting this? I believe factory girl no longer assumes ActiveRecord so you could theoretically create an isolated example that doesn't use rails at all.

Reply to this email directly or view it on GitHub:
https://github.com/rspec/rspec-rails/issues/1291#issuecomment-72106797

I'm just going to guess this is either a controller, request, or feature spec. And that the instance you are stubbing is what you get back from either FactoryGirl's create or build method. However, you are then loading a new object in the controller via find, where, etc. which is not the same object.

^^ :+1:

That seems like a good guess :)

In that case, what’s the best approach?

FWIW I’m trying to stub a method hook that is tested elsewhere, so I just want to return true so that I can test the actual request. It makes sense that the objects are different, but in this case how would I best stub the method response without globally stubbing it for all instances (as in with allow_any_instance_of)

—
Sent from Mailbox

On Thu, Jan 29, 2015 at 4:10 PM, Jon Rowe [email protected]
wrote:

^^ :+1:

Reply to this email directly or view it on GitHub:
https://github.com/rspec/rspec-rails/issues/1291#issuecomment-72123444

Don't use feature or request specs for that. Those are meant to be full stack acceptance / integration tests. If this is an issue of speed or something else, I suggest considering something like VCR to hook into capturing web requests, or conditionally loading fake stand-ins for external services.

If you wish to setup a stub like this, you'll need to utilize a controller spec.

I have a similar problem in a request spec.

it 'a' do
  CollectionPoint::RestartBleService.any_instance.stub(:perform)
  put restart_ble_collection_point_path(cp.id), headers: { 'Authorization': "Bearer #{@token}", 'content-type': 'application/json' }
  expect(response).to have_http_status(200)
end

and the controller:

  def restart_ble
    CollectionPoint::RestartBleService.new(@collection_point).perform
    if @collection_point.units.active.count == 0
      render json: { message: 'BLE restart failure' }, status: 480
    else
      render json: @collection_point.as_json(only: [:id, :beacon_uuid])
    end
  end

I want to stub :perform because CollectionPoint::RestartBleService.new(@collection_point).perform is tested in service specs. I would like to test http_status

class CollectionPoint::RestartBleService

  def initialize collection_point
    @uuid   = collection_point.beacon_uuid

    # below depends on strategy
    # if a Restart BLE action should only update UUID on active units then use below
    # @units  = collection_point.units.active
    # if it should try to salvage attached units even if they are not active then use below
    @units  = collection_point.units

    @parallel_requests_count = (ENV.fetch('PARALLEL_REQUESTS_COUNT') { 4 }).to_i
  end

  def perform
    Parallel.each(@units, in_processes: @parallel_requests_count) do |unit|
      CollectionPointUnit::RestartBleService.new(unit, @uuid).perform
    end
    CollectionPointUnit.connection.reconnect!
  end
end

Is this possible? :)

Workaround will be to stub find method:

allow(Article).to receive(:find).and_return(article) # article from factory bot
allow(article).to receive(:foobar).and_return(true)

Workaround will be to stub find method:

allow(Article).to receive(:find).and_return(article) # article from factory bot
allow(article).to receive(:foobar).and_return(true)

So far, I did as you mentioned. Work fine. However, I wish a more elegant way to solve it.

There isn't really an elegant way to do it, as its not really a recommended testing approach. Either deal with objects directly, or deal with the database if you are integration testing :)

Was this page helpful?
0 / 5 - 0 ratings