Reputation: 4290
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.
Upvotes: 54
Views: 44995
Reputation: 2537
A new helper method, capture_emails(&block), has been introduced since Rails 7.1.0. This method returns any emails sent within the block.
def test_emails
emails = capture_emails do
ContactMailer.welcome.deliver_now
end
assert_equal "Hi there", emails.first.subject
emails = capture_emails do
ContactMailer.welcome.deliver_now
ContactMailer.welcome.deliver_later
end
assert_equal "Hi there", emails.first.subject
end
For more details, refer to the PR.
Upvotes: 0
Reputation: 4716
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 .
Upvotes: 13
Reputation: 2401
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
Upvotes: 83
Reputation: 2543
A little late, but it may help others:
You could use Email-spec, a collection of Rspec/Minitest matchers and Cucumber steps.
Upvotes: 2
Reputation: 4568
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.
Upvotes: 21
Reputation: 19873
Here is the best way I've found to do it.
1) Include the action mailer callbacks plugin like this:
script/plugin install git://github.com/AnthonyCaliendo/action_mailer_callbacks.git
I don't really use the plugin's main features, but it does provide the nice functionality of being able to figure out which method was used to send an email.
2) Now you can put some methods in your test_helper.rb like this:
def assert_sent(method_name)
assert sent_num_times(method_name) > 0
end
def assert_not_sent(method_name)
assert sent_num_times(method_name) == 0
end
def assert_sent_once(method_name)
assert sent_num_times(method_name) == 1
end
def sent_num_times(method_name)
count = 0
@emails.each do |email|
count += 1 if method_name == email.instance_variable_get("@method_name")
end
count
end
3) Now you can write sweet tests like this:
require 'test_helper'
class MailingTest < ActionController::IntegrationTest
def setup
@emails = ActionMailer::Base.deliveries
@emails.clear
end
test "should send a mailing" do
assert_difference "Mailing.count", 1 do
feeds(:feed1).generate_mailing
end
assert_sent_once "broadcast"
assert_not_sent "failed_mailing"
end
end
Here "broadcast" and "mailing_failed" are the names of the methods in my ActionMailer::Base class. These are the ones you normally use by calling Mailer.deliver_broadcast(some_data)
or Mailer.deliver_failed_mailing(some_data)
etc. That's it!
Upvotes: 1
Reputation: 13972
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
Upvotes: 3
Reputation: 37915
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. Upvotes: 31