Rails - How do you test ActionMailer sent a specific email in tests

40,692

Solution 1

When using the ActionMailer during tests, all mails are put in a big array called deliveries. What you basically are doing (and is sufficient mostly) is checking if emails are present in the array. But if you want to specifically check for a certain email, you have to know what is actually stored in the array. Luckily the emails themselves are stored, thus you are able to iterate through the array and check each email.

See ActionMailer::Base to see what configuration methods are available, which you can use to determine what emails are present in the array. Some of the most suitable methods for your case probably are

  • recipients: Takes one or more email addresses. These addresses are where your email will be delivered to. Sets the To: header.
  • subject: The subject of your email. Sets the Subject: header.

Solution 2

As of rails 3 ActionMailer::Base.deliveries is an array of Mail::Message's. From the mail documentation:

#  mail['from'] = '[email protected]'
#  mail[:to]    = '[email protected]'
#  mail.subject 'This is a test email'
#  mail.body    = 'This is a body'
# 
#  mail.to_s #=> "From: [email protected]\r\nTo: you@...

From that it should be easy to test your mail's in an integration

mail = ActionMailer::Base.deliveries.last

assert_equal '[email protected]', mail['from'].to_s

assert_equal '[email protected]', mail['to'].to_s

Solution 3

Using current Rspec syntax, I ended up using the following:

last_email = ActionMailer::Base.deliveries.last
expect(last_email.to).to eq ['[email protected]']
expect(last_email.subject).to have_content 'Welcome'

The context of my test was a feature spec where I wanted to make sure a welcome email was sent to a user after signing up.

Solution 4

As of 2020 (Rails 6 era, probably introduced earlier) you can do the following: (using a SystemTest example) TL;DR: use assert_emails from ActionMailer::TestHelper and ActionMailer::Base.deliveries.last to access the mail itself.

require "application_system_test_case"
require 'test_helper'
require 'action_mailer/test_helper'

class ContactTest < ApplicationSystemTestCase
  include ActionMailer::TestHelper

  test "Send mail via contact form on landing page" do
    visit root_url

    fill_in "Message", with: 'message text'

    # Asserting a mail is sent
    assert_emails 1 do
      click_on "Send"
    end

    # Asserting stuff within that mail
    last_email  = ActionMailer::Base.deliveries.last

    assert_equal ['whatever'], last_email.reply_to
    assert_equal "contact", last_email.subject
    assert_match /Mail from someone/, last_email.body.to_s
  end
end

Official doc:

Note Instead of manually checking the content of the mail as in the system test above, you can also test whether a specific mailer action was used, like this:

assert_enqueued_email_with ContactMailer, :welcome, args: ["Hello", "Goodbye"]

And some other handy assertion, see https://api.rubyonrails.org/v6.0.3.2/classes/ActionMailer/TestHelper.html#method-i-assert_emails .

Solution 5

The test framework shoulda has an excellent helper which lets you assert certain conditions about an email that was sent. Yes, you could do it yourself with ActionMailer.deliveries, but shoulda makes it all one neat little block

Share:
40,692
robodisco
Author by

robodisco

Updated on September 24, 2020

Comments

  • robodisco
    robodisco over 3 years

    Currently in my tests I do something like this to test if an email is queued to be sent

    assert_difference('ActionMailer::Base.deliveries.size', 1) do       
      get :create_from_spreedly, {:user_id => @logged_in_user.id}
    end
    

    but if i a controller action can send two different emails i.e. one to the user if sign up goes fine or a notification to admin if something went wrong - how can i test which one actually got sent. The code above would pass regardless.

  • Malik Shahzad
    Malik Shahzad almost 10 years
    You can also use: "assert ActionMailer::Base.deliveries.size >= 1 (or == 2)" to check multiple mails are sent. and to verify multiple subjects, and emails use "ActionMailer::Base.deliveries.last(2)"
  • stevenspiel
    stevenspiel almost 7 years
    that's the have_sent_email matcher rubydoc.info/gems/shoulda/2.11.3/Shoulda/ActionMailer/Matche‌​rs (e.g. it { should have_sent_email.with_subject(/spam/).from('do-not-reply@exam‌​ple.com').with_body(‌​/spam/).to('myself@m‌​e.com') }