Search code examples
ruby-on-railsemailtestingrspecsidekiq

Testing email: last email is nil when sent through a background worker


I'm trying to add some tests for email using this railscast, however I'm getting undefined method 'to' for nil:NilClass.

The emails are actually processed through a background worker using Sidekiq and I know that they send properly since they work in production and also they would send before I added require 'sidekiq/testing' to spec_helper.rb

So basically my problem is how do I access the email in Sidekiq? I've tried to let the workers process the email but still got the same error.

spec/support/mailer_macros.rb

module MailerMacros
  def last_email
    ActionMailer::Base.deliveries.last
  end

  def reset_email
    ActionMailer::Base.deliveries = []
  end
end

spec/spec_helper.rb

require 'sidekiq/testing'
...
config.include MailerMacros
config.before(:each) { reset_email }

config/environments/test.rb

config.action_mailer.delivery_method = :test
config.action_mailer.default_url_options = { host: 'localhost:8080' }

controllers/tickets_controller.rb

def create
  @ticket = current_user.tickets.build(params[:ticket])   
  if @ticket.save
    Sidekiq::Client.enqueue(TicketNotifier, @ticket.id)
    flash[:success] = "Your ticket has been submitted successfully."
    redirect_to ticket_path(@ticket)
  else
    render 'new'
  end
end

part of the spec:

require 'spec_helper'

describe "Tickets" do
  subject { page }

  describe "when creating a new ticket successfully" do
    before do
      login_as_user
      visit new_ticket_path
      fill_in "Subject", with: "Test ticket"
      fill_in "Description", with: "This is a test ticket"
      select 'Billing', from: "ticket[category]" 
      click_button "Submit Ticket"
    end

    specify { last_email.to.should include("[email protected]") }
    # the rest tests the tickets#show view
    it { should have_selector('title', text: "Ticket #1") }
    it { should have_content("ticket has been submitted successfully") }
    ...
  end
end

When I use specify { ActionMailer::Base.deliveries.last.to.should include("[email protected]") } instead I get the same error.


Solution

  • When requiring sidekiq/testing and not sidekiq/testing/inline, the jobs are put on the queue but not executed until a command like this is given:

    Sidekiq::Extensions::DelayedMailer.drain
    

    I use a helper method like this in my specs:

    def process_async
      EventWorker.drain
      Sidekiq::Extensions::DelayedMailer.drain
    end
    

    Where EventWorker might be a custom worker in my app, and Sidekiq::Extensions::DelayedMailer is where delayed mails go by default with Sidekiq.

    I can then have a spec like this:

    #do stuff that should send an email and trigger other sidekiq events
    ...
    process_async
    ...
    #assert the email was sent correctly
    

    There is a great explanation of all this on the SideKiq wiki: https://github.com/mperham/sidekiq/wiki/Testing