Search code examples
ruby-on-railsrspecactionmailerruby-on-rails-4.2

Test delayed job mailers in Rails method


I have a rails method which allows a user to submit a review and to counterparty, it sends an email using the delayed jobs.

def update_review
  @review.add_review_content(review_params)
  ReviewMailer.delay.review_posted(@review.product_owner, params[:id])
end

And I am trying to add a rspec test for this to check if the mailer is delivered properly and to whom. The delayed jobs run immediately after they are created on test because I want other jobs like the job to update the product owners overall rating to be completed immediately.

So the email does get fired but how can I add a test for it?

EDIT: Adding current tests

My current tests are:

describe 'PUT #update the review' do

  let(:attr) do
    { rating: 3.0, raw_review: 'Some example review here' }
   end

   before(:each) do
     @review = FactoryBot.create :review
     put :update, id: @review.id, review: attr
   end

   it 'creates a job' do
     ActiveJob::Base.queue_adapter = :test
     expect {
       AdminMailer.review_posted(@coach, @review.id).deliver_later
     }.to have_enqueued_job
   end 

   it { should respond_with 200 }

end

This does test that the mailer works properly but I want to test that it gets triggered properly in the method flow as well.


Solution

  • It sounds like what you want is to ensure that the update_review method enqueues a job to send the correct email to the correct recipient. Here's a simpler way to accomplish that:

    describe 'PUT #update the review' do
      let(:params) { { rating: rating, raw_review: raw_review } }
      let(:rating) { 3.0 }
      let(:raw_review) { 'Some example review here' }
      let(:review) { FactoryBot.create(:review) }
      let(:delayed_review_mailer) { instance_double(ReviewMailer) } 
    
      before do
        # assuming this is how the controller finds the review...
        allow(Review).to receive(:find).and_return(review)
    
        # mock the method chain that enqueues the job to send the email
        allow(ReviewMailer).to receive(:delay).and_return(delayed_review_mailer)
        allow(delayed_review_mailer).to receive(:review_posted)
    
        put :update, id: review.id review: params
      end
    
      it 'adds the review content to the review' do
        review.reload
        expect(review.rating).to eq(rating) 
        expect(review.raw_review).to eq(raw_review)
      end
    
      it 'sends a delayed email' do
        expect(ReviewMailer).to have_received(:delay)
      end
    
      it 'sends a review posted email to the product owner' do
        expect(delayed_review_mailer)
          .to have_received(:review_posted)
          .with(review.product_owner, review.id)
      end
    end
    

    The reason I prefer this approach is that a) it could be done without touching the database at all (by swapping the factory for an instance double), and b) it doesn't try to test parts of Rails that were already tested by the folks who built Rails, like ActiveJob and ActionMailer. You can trust Rails' own unit tests for those classes.