denniss
denniss

Reputation: 17599

Setting expectation on resque .perform method. Task is enqueued in a Callback

So based on my understanding, I beleive when you do

Resque.inline = Rails.env.test?

Your resque tasks will run synchronously. I am writing a test on resque task that gets enqueue during an after_commit callback.

after_commit :enqueue_several_jobs

#class PingsEvent < ActiveRecord::Base
...
   def enqueue_several_jobs
      Resque.enqueue(PingFacebook, self.id)
      Resque.enqueue(PingTwitter, self.id)
      Resque.enqueue(PingPinterest, self.id)
   end

In the .perform methd of my Resque task class, I am doing a Rails.logger.info and in my test, I am doing something like

..
Rails.logger.should_receive(:info).with("PingFacebook sent with id #{dummy_event.id}")
PingsEvent.create(params)

And I have the same test for PingTwitter and PingPinterest.

I am getting failure on the 2nd and third expectation because it seems like the tests actually finish before all the resque jobs get run. Only the first test actually passes. RSpec then throws a MockExpectationError telling me that Rails.logger did not receive .info for the other two tests. Anyone has had experience with this before?

EDIT

Someone mentioned that should_receive acts like a mock and that I should do .exactly(n).times instead. Sorry for not making this clear earlier, but I have my expectations in different it blocks and I don't think a should_receive in one it block will mock it for the next it block? Let me know if i'm wrong about this.

Upvotes: 2

Views: 1093

Answers (2)

Parker Selbert
Parker Selbert

Reputation: 1546

Using should_receive behaves like a mock. Having multiple expectations on the same object with different arguments won't work. If you change the expectation to Rails.logger.should_receive(:info).exactly(3).times your spec will probably past.

All that said, you may want to assert something more pertinent than what is being logged for these specs, and then you could have multiple targeted expectations.

The Rails.logger does not get torn down between specs, so it doesn't matter if the expectations are in different examples. Spitting out the logger's object id for two separate examples illustrates this:

it 'does not tear down rails logger' do
  puts Rails.logger.object_id # 70362221063740
end

it 'really does not' do
  puts Rails.logger.object_id # 70362221063740
end

Upvotes: 1

zetetic
zetetic

Reputation: 47568

class A
  def bar(arg)
  end

  def foo
    bar("baz")
    bar("quux")
  end
end

describe "A" do
  let(:a) { A.new }

  it "Example 1" do
    a.should_receive(:bar).with("baz")
    a.foo # fails 'undefined method bar'
  end
  it "Example 2" do
    a.should_receive(:bar).with("quux")
    a.foo # fails 'received :bar with unexpected arguments
  end
  it "Example 3" do
    a.should_receive(:bar).with("baz")
    a.should_receive(:bar).with("quux")
    a.foo # passes
  end
  it "Example 4" do
    a.should_receive(:bar).with(any_args()).once
    a.should_receive(:bar).with("quux")
    a.foo # passes
  end
end

Like a stub, a message expectation replaces the implementation of the method. After the expectation is fulfilled, the object will not respond to the method call again -- this results in 'undefined method' (as in Example 1).

Example 2 shows what happens when the expectation fails because the argument is incorrect.

Example 3 shows how to stub multiple invocations of the same method -- stub out each call with the correct arguments in the order they are received.

Example 4 shows that you can reduce this coupling somewhat with the any_args() helper.

Upvotes: 2

Related Questions