bricker
bricker

Reputation: 8941

Rspec should_receive on an instance not behaving

I have a class method that just iterates through some records and performance an instance method on each record. I am trying to write a simple test for the class method just to make sure that it's sending the instance method to each record.

it "fires any pending alarms" do
  pending       = create :alarm, :pending
  other_pending = create :alarm, :pending

  # sanity check
  Alarm.pending.sort.should eq [pending, other_pending].sort

  pending.should_receive(:fire)
  other_pending.should_receive(:fire)

  Alarm.fire_pending
end

alarm.rb

class Alarm < ActiveRecord::Base
  scope :pending, where(pending: true)

  def self.fire_pending
    self.pending.each do |alarm|
      alarm.fire
    end
  end
end

But I am receiving an error on both should_receive expectations:

 Failure/Error: pending.should_receive(:fire)
   (#<Alarm:0x007fcc96334310>).fire(any args)
       expected: 1 time
       received: 0 times

I have used this expectation before successfully. I'm afraid that I'm missing something obvious.

edit

I've worked around it for now with this:

it "fires any pending alarms" do
  pending       = create :alarm, :pending
  other_pending = create :alarm, :pending

  pending_alarms = Alarm.pending
  pending_alarms.sort.should eq [pending, other_pending].sort

  Alarm.stub(:pending) { pending_alarms }
  pending_alarms.first.should_receive(:fire)
  pending_alarms.last.should_receive(:fire)

  Alarm.fire_pending
end

But I don't really like that and would like to know why my first try doesn't work.

Upvotes: 1

Views: 1770

Answers (1)

David Chelimsky
David Chelimsky

Reputation: 9000

The objects returned by Alarm.pending are not the same instances created in the example. You can see this by changing:

pending_alarms.sort.should eq [pending, other_pending].sort

to:

# the eq matcher uses == (object equivalence)
# the equal matcher uses equals? (object identity)
pending_alarms.sort.tap do |sorted|
  sorted[0].should eq pending       # passes
  sorted[1].should eq other_pending # passes
  sorted[0].should equal pending       # fails
  sorted[1].should equal other_pending # fails
end

This is a good case for using (the evil) any_instance:

it "fires any pending alarms" do
  pending = create :alarm, :pending
  Pending.any_instance.should_receive(:fire)
  Alarm.fire_pending
end

Upvotes: 6

Related Questions