Test ActionMailer `deliver_later` in RSpec Controller Tests

A lot can happen when a Rails controller action gets called. This includes transactional emails getting queued up for delivery. To ensure our controller’s behavior stays consistent as our app evolves we can write RSpec tests.

Among other things these …


This content originally appeared on DEV Community and was authored by Josh Branchaud

A lot can happen when a Rails controller action gets called. This includes transactional emails getting queued up for delivery. To ensure our controller's behavior stays consistent as our app evolves we can write RSpec tests.

Among other things these tests can ensure that transactional emails get queued for delivery at the appropriate times.

This post documents a couple different methods I've used for those tests.

ActionMailer::Base.deliveries

If you have your queue_adapter set to :inline, then a deliver_later will happen synchronously. So, the email will immediately end up in the deliveries box.

describe '#welcome' do
  it 'sends the welcome email to the user' do
    valid_params = { user_id: user.id }

    expect {
      post :invite, params: valid_params
    }.to change { ActionMailer::Base.deliveries.count }.by(1)
  end
end

At this point you could even write an additional test to look at properties of the email that was sent, like who it was sent to and what the subject line said.

have_enqueued_job

The behavior is a bit different if your queue_adapter is set to something like :test or async. In this case, the email is going to be queued in the app's job queue. Since it is not immediately being sent, the expectation will have to be about the job queue instead.

describe '#welcome' do
  it 'sends the welcome email to the user' do
    valid_params = { user_id: user.id }

    expect {
      post :invite, params: valid_params
    }.to have_enqueued_job(ActionMailer::DeliveryJob)
  end
end

We can even dig into more specifics about what mailer class and method were invoked, like this:

describe '#welcome' do
  it 'sends the welcome email to the user' do
    valid_params = { user_id: user.id }

    expect {
      post :invite, params: valid_params
    }.to have_enqueued_job(ActionMailer::DeliveryJob)
      .with('UserMailer', 'welcome', 'deliver_now', Integer)
  end
end

docs

Receive Block and Mail Double

This approach mocks the mailer so that we can test that deliver_later gets called. We take things a step further with the receive method by using its &block argument to make assertions about the values passed to the mailer method.

describe '#welcome' do
  it 'sends the welcome email to the user' do
    mail_double = double
    allow(mail_double).to receive(:deliver_later)

    expect(UserMailer).to receive(:welcome) do |user_id|
      expect(user_id).to match(user.id)
    end.and_return(mail_double)

    valid_params = { user_id: user.id }

    post :invite, params: valid_params
  end
end

ActionMailer RSpec Matcher

The previous approach requires a bit of boilerplate setup. If There is a way to go the (instance) double route, without duplicating this setup over and over. That can be achieved with a custom RSpec matcher. I've used some version of the following on many Rails projects.

# spec/support/mailer_matcher.rb
require "rspec/expectations"

RSpec::Matchers.define :send_email do |mailer_action|
  match do |mailer_class|
    message_delivery = instance_double(ActionMailer::MessageDelivery)
    expect(mailer_class).to receive(mailer_action).and_return(message_delivery)
    allow(message_delivery).to receive(:deliver_later)
  end
end

Assuming the spec helper requires support files, this custom matcher will be available in your specs. Here is how to use it.

describe '#welcome' do
  it 'sends the welcome email to the user' do
    expect(UserMailer).to send_email(:welcome)

    valid_params = { user_id: user.id }

    post :invite, params: valid_params
  end
end

These are the approaches I know about and use. If I'm missing an approach to testing ActionMailer, drop a note. I'd love to see how you're doing it.

If you enjoy my writing, consider joining my newsletter or following me on twitter.

References:

Cover photo by Timothy Eberly on Unsplash


This content originally appeared on DEV Community and was authored by Josh Branchaud


Print Share Comment Cite Upload Translate Updates
APA

Josh Branchaud | Sciencx (2021-06-29T00:16:17+00:00) Test ActionMailer `deliver_later` in RSpec Controller Tests. Retrieved from https://www.scien.cx/2021/06/29/test-actionmailer-deliver_later-in-rspec-controller-tests/

MLA
" » Test ActionMailer `deliver_later` in RSpec Controller Tests." Josh Branchaud | Sciencx - Tuesday June 29, 2021, https://www.scien.cx/2021/06/29/test-actionmailer-deliver_later-in-rspec-controller-tests/
HARVARD
Josh Branchaud | Sciencx Tuesday June 29, 2021 » Test ActionMailer `deliver_later` in RSpec Controller Tests., viewed ,<https://www.scien.cx/2021/06/29/test-actionmailer-deliver_later-in-rspec-controller-tests/>
VANCOUVER
Josh Branchaud | Sciencx - » Test ActionMailer `deliver_later` in RSpec Controller Tests. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2021/06/29/test-actionmailer-deliver_later-in-rspec-controller-tests/
CHICAGO
" » Test ActionMailer `deliver_later` in RSpec Controller Tests." Josh Branchaud | Sciencx - Accessed . https://www.scien.cx/2021/06/29/test-actionmailer-deliver_later-in-rspec-controller-tests/
IEEE
" » Test ActionMailer `deliver_later` in RSpec Controller Tests." Josh Branchaud | Sciencx [Online]. Available: https://www.scien.cx/2021/06/29/test-actionmailer-deliver_later-in-rspec-controller-tests/. [Accessed: ]
rf:citation
» Test ActionMailer `deliver_later` in RSpec Controller Tests | Josh Branchaud | Sciencx | https://www.scien.cx/2021/06/29/test-actionmailer-deliver_later-in-rspec-controller-tests/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.