English 中文(简体)
Rails - How do you test ActionMailer sent a specific email in tests
原标题:

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.

最佳回答

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.
问题回答

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

#  mail[ from ] =  mikel@test.lindsaar.net 
#  mail[:to]    =  you@test.lindsaar.net 
#  mail.subject  This is a test email 
#  mail.body    =  This is a body 
# 
#  mail.to_s #=> "From: mikel@test.lindsaar.net
To: you@...

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

mail = ActionMailer::Base.deliveries.last

assert_equal  mikel@test.lindsaar.net , mail[ from ].to_s

assert_equal  you@test.lindsaar.net , mail[ to ].to_s

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

last_email = ActionMailer::Base.deliveries.last
expect(last_email.to).to eq [ test@example.com ]
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.

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 .

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

A little late, but it may help others:

You could use Email-spec, a collection of Rspec/Minitest matchers and Cucumber steps.

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!





相关问题
rails collection_select vs. select

collection_select and select Rails helpers: Which one should I use? I can t see a difference in both ways. Both helpers take a collection and generates options tags inside a select tag. Is there a ...

SSL slowness in EC2

We ve deployed our rails app to EC2. In our setup, we have two proxies on small instances behind round-robin DNS. These run nginx load balancers for a dynamically growing and shrinking farm of web ...

Auth-code with A-Za-z0-9 to use in an URL parameter

As part of a web application I need an auth-code to pass as a URL parameter. I am currently using (in Rails) : Digest::SHA1.hexdigest((object_id + rand(255)).to_s) Which provides long strings like : ...

RubyCAS-Client question: Rails

I ve installed RubyCAS-Client version 2.1.0 as a plugin within a rails app. It s working, but I d like to remove the ?ticket= in the url. Is this possible?

activerecord has_many :through find with one sql call

I have a these 3 models: class User < ActiveRecord::Base has_many :permissions, :dependent => :destroy has_many :roles, :through => :permissions end class Permission < ActiveRecord::...

Ordering a hash to xml: Rails

I m building an xml document from a hash. The xml attributes need to be in order. How can this be accomplished? hash.to_xml

Text Editor for Ruby-on-Rails

guys which text editor is good for Rubyonrails? i m using Windows and i was using E-Texteditor but its not free n its expired now can anyone plese tell me any free texteditor? n which one is best an ...

How to get SQL queries for each user where env is production

I’m developing an application dedicated to generate statistical reports, I would like that user after saving their stat report they save sql queries too. To do that I wrote the following module: ...

热门标签