ActionMailer::Base.deliveries always empty in RSpec model test in Rails 4 - ruby-on-rails

I have the following failing test:
describe Image do
describe 'a_method' do
it 'sends email' do
Image.count.should == 1
expect do
ImageMailer.deleted_image(Image.last.id).deliver
end.to change(ActionMailer::Base.deliveries, :length)
end
end
end
And here's my mailer:
class ImageMailer < ActionMailer::Base
layout 'email'
default from: 'whee#example.com'
def deleted_image image_id, recipient='whee#example.com'
#image = Image.find(image_id)
subject = "Image email"
mail(to: recipient, subject: subject) do |format|
format.text
format.html { render layout: 'email' }
end
end
end
My test fails with Failure/Error: expect do length should have changed, but is still 0. I have another test for my mailer itself and it passes:
describe ImageMailer do
it 'should deliver the mail' do
expect do
subject.deliver
end.to change { ActionMailer::Base.deliveries.length }.by(1)
end
end
I don't know why ActionMailer::Base.deliveries is always empty in my model spec but not in my mailer spec. The mail obviously works. My model test was originally different, testing whether a method on my model caused an email to be sent, but when that failed to generate a mail delivery, I explicitly tried the ImageMailer.deleted_image(Image.last.id).deliver line and it didn't work. Is there something special about RSpec tests where the object being described is a mailer class?
Here are some relevant lines from my config/environments/test.rb file:
config.action_mailer.delivery_method = :test
config.action_mailer.default_url_options = {host: 'localhost:3000'}
config.action_mailer.perform_deliveries = true

A combination of should_receive with and_return got my test to pass:
it 'send email for an image not in Amazon that is in our table' do
mailer = double
mailer.should_receive(:deliver)
ImageMailer.should_receive(:deleted_image).and_return(mailer)
ImageMailer.deleted_image(Image.last.id).deliver
end
And when I comment out ImageMailer.deleted_image(Image.last.id).deliver, the test fails as expected. From this, I was able to replace ImageMailer.deleted_image(Image.last.id).deliver with my actual test where I check that calling a method on my model causes an email to be sent.

Related

Best way to test a mailer with arguments

I have a mailer that passes an argument like so:
AnimalMailer.daily_message(owner).deliver_later
The method looks like this:
AnimalMailer
class AnimalMailer < ApplicationMailer
def daily_message(owner)
mail(
to: "#{user.name}",
subject: "test",
content_type: "text/html",
date: Time.now.in_time_zone("Mountain Time (US & Canada)")
)
end
end
I'm new to writing specs and was wondering how should I pass the owner to the method and test it. I currently have this set up:
require "rails_helper"
RSpec.describe AnimalMailer, type: :mailer do
describe "monthly_animal_message" do
let(:user) { create(:user, :admin) }
it "renders the headers" do
expect(mail.subject).to eq("test")
expect(mail.to).to eq(user.name)
end
end
end
Specs generally follow a three-step flow 1) set up, 2) invoke, 3) expect. This applies for unit testing mailers like anything else. The invocation and parameters are the same in the test as for general use, so in your case:
RSpec.describe AnimalMailer, type: :mailer do
describe "monthly_campaign_report" do
let(:user) { create(:user, :admin) }
let(:mail) { described_class.daily_message(user) } # invocation
it 'renders the headers' do
expect(mail.subject).to eq('test')
expect(mail.to).to eq(user.name)
end
it 'renders the body' do
# whatever
end
end
end
Note that since the describe is the class name being tested, you can use described_class from there to refer back to the described class. You can always use AnimalMailer.daily_message as well, but among other things described_class ensures that if you shuffle or share examples that you are always testing what you think you are.
Also note that in the case of unit testing a mailer, you're mostly focused on the correct generation of the content. Testing of successful delivery or use in jobs, controllers, etc., would be done as part of request or feature tests.
Before testing it, make sure the config / environment / test.rb file is set to:
config.action_mailer.delivery_method = :test
This ensures that emails are not actually sent, but are stored in the ActionMailer :: Base.deliveries array.
Following Four-Phase Test :
animal_mailer.rb
class AnimalMailer < ApplicationMailer
default from: 'noreply#animal_mailer.com'
def daily_message(owner)
#name = owner.name
mail(
to: owner.email,
subject: "test",
content_type: "text/html",
date: Time.now.in_time_zone("Mountain Time (US & Canada)")
)
end
end
animal_mailer_spec.rb
RSpec.describe AnimalMailer, type: :mailer do
describe 'instructions' do
let(:user) { create(:user, :admin) }
let(:mail) { described_class.daily_message(user).deliver_now }
it 'renders the subject' do
expect(mail.subject).to eq("test")
end
it 'renders the receiver email' do
expect(mail.to).to eq([user.email])
end
it 'renders the sender email' do
expect(mail.from).to eq(['noreply#animal_mailer.com'])
end
it 'assigns #name' do
expect(mail.body.encoded).to match(user.name)
end
end
end
if you have a model user:
class User
def send_instructions
AnimalMailer.instructions(self).deliver_now
end
end
RSpec.describe User, type: :model do
subject { create :user }
it 'sends an email' do
expect { subject.send_instructions }
.to change { ActionMailer::Base.deliveries.count }.by(1)
end
end

Can not test mailer action called in rspec rails

In mailer of rails, as I know all method will be class method.
But I can not test my mailer method called:
user_mailer_spec.rb:
it "should call send_notifition method" do
#user = FactoryGirl.build(:user)
notify_email = double(:send_notifition)
expect(UsersMailer.new).to receive(:notify_email).with(#user)
#user.save
end
user_mailer.rb:
def notify(user)
mail to: user.email, subject: "Example"
end
user.rb:
after_commit :send_notifition
private
def send_notifition
UsersMailer.notify(self)
end
The above codes will not pass but when I change notifition to self.notifition, it pass:
def self.notify(user)
mail to: user.email, subject: "Example"
end
First of all, I'd like to point you to an awesome gem for testing emails: https://github.com/email-spec/email-spec.
I think the problem is that you're asserting on UsersMailer.new, thus putting a mock on a different instance than the one then being instantiated by the User model. I generally test emails like this without any issues:
it "should call send_notifition method" do
#user = FactoryGirl.build(:user)
mail = double(:mail)
expect(UsersMailer).to receive(:notify_email).with(#user).and_return(mail)
expect(mail).to receive(:deliver_later) # or deliver_now, if you don't use a background queue
#user.save
end
Note how I'm doing expect(UsersMailer) instead of expect(UsersMailer.new) and also take not that I'm asserting that the email is actually delivered (I think a deliver statement is missing in your code).
Hope that helps.
Solved:
Thank you #Clemens Kofler for supporting.
I have many mistaking in my code:
First: No need to install gem "email_spec", and change user.rb file
from
after_commit :send_notifition
private
def send_notifition
UsersMailer.notify(self)
end
to
after_commit :send_notifition
private
def send_notifition
UsersMailer.notify(self).deliver
end
Second: Change user_mailer_spec.rb file
from
it "should call send_notifition method" do
#user = FactoryGirl.build(:user)
expect(#user).to receive(:send_notifition)
notify_email = double(:send_notifition)
expect(UsersMailer.new).to receive(:notify_email).with(#user)
#user.save
end
to
it "should call send_notifition_mail_if_created_new_hospital method" do
#user = FactoryGirl.build(:user)
# I don't know why "expect(#user).to receive(:send_notifition)" not passed here
mail = double(:mail)
expect(UsersMailer).to receive(:notify_email).with(#user).and_return(mail)
allow(mail).to receive(:deliver)
#user.save
end
Finally: config mailer in config/environments/test.rb for test environment can use mailer (because spec run in test env)

In Rails why is my mail body empty only in my test?

I have an actionmailer class and associated overhead, it works perfectly. In my unit test (rails default minitest) however, the email body is empty. Why is that?
my mailer class:
class TermsMailer < ApplicationMailer
default from: "info#domain.com"
def notice_email(user,filename)
#user = user
#file = filename
mail(to: "info#domain.com", subject: 'Data downloaded')
end
end
my test:
require 'test_helper'
class TermsMailerTest < ActionMailer::TestCase
test "notice" do
email = TermsMailer.notice_email(users(:me),'file.ext').deliver_now
assert_not ActionMailer::Base.deliveries.empty?
assert_equal ['info#domain.com'], email.from
assert_equal ['info#domain.com'], email.to
assert_equal 'Data downloaded', email.subject
assert_equal 'arbitrary garbage for comparison', email.body.to_s
end
end
The views for this mailer are not blank, and the correct contents are in fact sent in the emails. So why is the email body blank in my test?
You probably have two versions of the email templates (text.erb and html.erb).
If so, you can use email.text_part.body.to_s for plain-text email and email.html_part.body.to_s for HTML version.

mail.perform_deliveries = false doesn't work in an ActionMailer interceptor

We're using an ActionMailer interceptor like this:
class MailerInterceptor
def self.delivering_email(message)
p 1
if message.to.include?("test2#example.com")
p 2
message.perform_deliveries = false
end
end
end
Mailer.register_interceptor(MailerInterceptor)
But it doesn't seem to block sending of messages to that address in production or in our test:
require 'spec_helper'
describe "MailerInterceptor" do
it "should block sending to certain addresses" do
expect{ Mailer.user_alert("test1", "test#example.com").deliver }.to change{ ActionMailer::Base.deliveries.count }.by(1)
expect{ Mailer.user_alert("test2", "test2#example.com").deliver }.to_not change{ ActionMailer::Base.deliveries.count }
end
end
This is with rails 3.2.14.
It prints '2' on the second line of the test, but still delivers the email. Any ideas? Thanks!
Please check if you are using ActionMailer #delivery method and not #delivery! because second one skips checking perform_deliveries.
There is some more details required in your code although I can suggest some thing that may be help you
Mailer.register_interceptor(MailerInterceptor)
it should be
ActionMailer::Base.register_interceptor(MailerInterceptor)
Secondly your message.perform_deliveries = false should work do not know why it is not working
you can simply try
ActionMailer::Base.delivery_method = :test
instead of
message.perform_deliveries = false

ActionMailer::Base.deliveries always empty in Rspec

I'm trying to test some mailers with rspec but deliveries are always empty. Here is my rspec test:
require "spec_helper"
describe "AccountMailer", :type => :helper do
before(:each) do
ActionMailer::Base.delivery_method = :test
ActionMailer::Base.perform_deliveries = true
ActionMailer::Base.deliveries = []
end
it "should send welcome email to account email" do
account = FactoryGirl.create :account
account.send_welcome_email
ActionMailer::Base.deliveries.empty?.should be_false
ActionMailer::Base.deliveries.last.to.should == account.email
end
end
It fails with:
1) AccountMailer should send welcome email to account email
Failure/Error: ActionMailer::Base.deliveries.empty?.should be_false
expected true to be false
My send_welcome_email function looks like this ( that's my model ):
def send_welcome_email
AccountMailer.welcome self
end
And my mailer:
class AccountMailer < ActionMailer::Base
default from: APP_CONFIG['email']['from']
def welcome data
if data.locale == 'es'
template = 'welcome-es'
else
template = 'welcome-en'
end
mail(:to => data.email, :subject => I18n.t('welcome_email_subject'), :template_name => template)
end
end
Any ideas? I'm not sure about how to proceed.
Have you tested that it's working when you're actually running the app? Perhaps your test is correct to be failing.
I noticed that you're never calling deliver when you create the mail, so I suspect that the test is failing because email is, in fact, not getting sent. I would expect your send_welcome_email method to look more like
def send_welcome_email
AccountMailer.welcome(self).deliver
end

Resources