Rails/Rspec: Testing delayed_job mails
Just wondering how to test that actionmailer requests are actually sent to the delayed_job que in rspec.
I would have assumed it was quite simple, but my delayed_job queue doesn't seem to be incrementing. Code below:
Controller:
def create
@contact = Contact.new(params[:contact])
if @contact.save
contactmailer = ContactMailer
contactmailer.delay.contact_message(@contact)
redirect_to(contacts_url)
else
render :action => "new"
end
Spec:
it "queues mail when a contact is created" do
expectedcount = Delayed::Job.count + 1
Contact.stub(:new).with(mock_contact()) { mock_contact(:save => true) }
post :create, :contact => mock_contact
expectedcount.s开发者_运维知识库hould eq(Delayed::Job.count)
end
Both before and after the call to the controller, the Delayed::Job.count returns 0. I've tried taking the conditional out of the controller, but I still can't get the delayed job count to increment.
Any suggestions appreciated - cheer
You can also test what the jobs will do by running them or turning off queuing.
Tweak config whenever you want (i.e. in a before :each
block).
Delayed::Worker.delay_jobs = false
or perform your saved jobs
Delayed::Worker.new.work_off.should == [1, 0]
I have been using this method happily for a while. For one thing, using the new any_instance
support in RSpec, you can test your delayed methods effects directly. However, I've found tests that use work_off
to be slow.
What I usually do now is:
mock_delay = double('mock_delay').as_null_object
MyClass.any_instance.stub(:delay).and_return(mock_delay)
mock_delay.should_receive(:my_delayed_method)
Then I have a separate spec for my_delayed_method
. This is much faster, and probably better unit testing practice -- particularly for controllers. Though if you're doing request specs or other integration-level specs, then you probably still want to use work_off
.
I think your mock object is somehow introducing an error -- it's hard to tell exactly how without seeing the definition of the mock_contact
method.
In any case, you might try something along these lines:
it "queues mail when a contact is created" do
Contact.stub(:new) { mock_model(Contact,:save => true) }
Delayed::Job.count.should == 0
post :create, {}
Delayed::Job.count.should == 1
end
or the sexier version (caveat: I always end up doing it the non-sexy way):
it "queues mail when a contact is created" do
Contact.stub(:new) { mock_model(Contact,:save => true) }
expect {
post :create, {}
}.to change(Delayed::Job.count).by(1)
end
You can also follow the convention (from Railscast 275) of
ActionMailer::Base.deliveries.last.to.should == user.email
but instead do this:
Delayed::Job.last.handler.should have_content(user.email)
This thread is a bit old, but here is my go at it:
Create a function expect_jobs
def expect_jobs n, time = nil
expect(Delayed::Job.count).to eq(n)
Timecop.travel(time) unless time.nil?
successes, failures = Delayed::Worker.new.work_off
expect(successes).to eq(n)
expect(failures).to eq(0)
expect(Delayed::Job.count).to eq(0)
Timecop.travel(Time.now) unless time.nil?
end
Then simply call it before checking if the callback has done its job. eg:
it "sends a chapter to the admin user" do
post :chapter_to_user, { chapter: @book.chapters.first}
expect_jobs(1)
SubscribeMailer.should have(1).delivery
SubscribeMailer.deliveries.should have(1).attachment
end
This seems to work on my side, and allows me to run both my delayed jobs and my methods.
@zetetic I think we have to pass block in change method here.
It shoulb be like this:
it "queues mail when a contact is created" do
Contact.stub(:new) { mock_model(Contact,:save => true) }
expect {
post :create, {}
}.to change { Delayed::Job.count }.by(1)
end
精彩评论