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.
Related
Trying to create an Rspec/Factory girl test to make sure that Devise's confirmation on signup is covered - the site has 3 languages (Japanese, English, Chinese) so I want to make sure nothing breaks the signup process.
I have the following factories:
user.rb << Has everything needed for the general user mailer tests
signup.rb which has:
FactoryGirl.define do
factory :signup do
token "fwoefurklj102939"
email "abcd#ek12o9d.com"
end
end
The devise user_mailer method that I want to test is:
def confirmation_instructions(user, token, opts={})
#user = user
set_language_user_only
mail to: #user.email,
charset: (#user.language == User::LANGUAGE_JA ? 'ISO-2022-JP' : 'UTF8')
end
I cannot for the life of me figure out how to get the token part to work in the test - any advice or ideas?
I have been trying something along these lines (to check the email is being sent) without success:
describe UserMailer, type: :mailer do
describe "sending an email" do
after(:all) { ActionMailer::Base.deliveries.clear }
context "Japanese user emails" do
subject(:signup) { create(:signup) }
subject(:user) { create(:user) }
subject(:mail) do
UserMailer.confirmation_instructions(user, token, opts={})
end
it "sends an email successfully" do
expect { mail.deliver }.to change { ActionMailer::Base.deliveries.size }.by(1)
end
end
end
end
The resulting error is undefined local variable or methodtoken'and I cannot work out why it is not coming from thesignup` factory. I tried changing
subject(:mail) do
UserMailer.confirmation_instructions(user, token, opts={})
end
to
subject(:mail) do
UserMailer.confirmation_instructions(user, signup.token, opts={})
end
but then I received this error:
Failure/Error: subject(:signup) { create(:signup) }
NameError:
uninitialized constant Signup
EDIT: I forgot to mention something important - the actual code all works for user signups in all 3 languages, so I am certain that this is definitely my inexperience with testing at fault.
subject(:mail) do
UserMailer.confirmation_instructions(user, user.confirmation_token)
end
This varies of course depending on what your exact implementation is but your user class should be generating the token:
require 'secure_random'
class User
before_create :generate_confirmation_token!
def generate_confirmation_token!
confirmation_token = SecureRandom.urlsafe_base64
end
end
Creating a separate factory is unnecessary and won't work since FactoryGirl will try to create an instance of Signup which I'm guessing that you don't have.
Factories are not fixtures.
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)
When I run rake test test:all in my rails App I get the following failure:
NotificationsMailerTest#test_my_mails
--- expected
+++ actual
## -1 +1,2 ##
-"Hello Kyle [John] Here is the information FROM THE APPLICATION! This is my email."
+"Hello Kyle [John] Here is the information FROM THE APPLICATION! This is my email.
+"
Notice that the endquote of the actual string is on another line.
The following represents the mailer test, and finally the view corresponding to the Mailer's
send_application function.
notifications_mailer_test.rb
require 'test_helper'
class NotificationsMailerTest < ActionMailer::TestCase
test "my mails" do
# Send the email, then test that it got queued
email = NotificationsMailer.send_application("This is my email.").deliver
assert_not ActionMailer::Base.deliveries.empty?
# Test the body of the sent email contains what we expect it to
assert_equal ['from#blag.net'], email.from
assert_equal ['kyle#blag.net'], email.to
assert_equal 'New Application', email.subject
assert_equal "Hello Kyle [John] Here is the information FROM THE APPLICATION! This is my email.", email.body.to_s
end
end
send_application.text.erb
Hello Kyle [John] Here is the information FROM THE APPLICATION! <%= #message%>
and here is my NotificationsMailer and the corresponding send_application func:
class NotificationsMailer < ActionMailer::Base
default :from => "from#blag.net"
default :to => "kyle#blag.net"
def send_application(body)
#message = body
delivery_options = {
user_name: "from#blag.net",
password: "laowowtze",
address: "secure3434.mostlator.com"
}
mail(:subject => "New Application")
end
end
Probably your text editor added a new line at the end of the file send_application.text.erb.
You can also remove surrounding white spaces from body with strip method.
assert_equal "Hello Kyle [John] Here is the information FROM THE APPLICATION! This is my email.", email.body.to_s.strip
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.
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