We can check enqueued_jobs.length, provided that email is the only possible type of background job.
it 'sends exactly one, particular email' do
expect { post :create }.to(
# First, we can check about the particular email we care about.
have_enqueued_mail(MyMailer, :my_particular_email)
)
# But we also have to check that no other email was sent.
expect(ActiveJob::Base.queue_adapter.enqueued_jobs.length).to eq(1)
end
Is there a better way to assert that:
MyMailer.my_particular_email was enqueued,
no other email was enqueued,
and we don't care if other, non-email background jobs were enqueued
I believe once will work with this matcher.
expect {
post :create
}.to have_enqueued_mail(MyMailer, :my_particular_email).once
# first filter MyMailer job
my_mail_jobs = ActiveJob::Base.queue_adapter.enqueued_jobs.select { |job|
job[:job] == SendEmailsJob &&
job[:args][0] == "MyMailer"
}
# check only once
expect(my_mail_jobs.length).to eq(1)
# and that send to your particular email not other email
expect(my_mail_jobs.first[:args]).to include("your particular email")
Related
I have a worker that sends email to users when new feedback pops in. I wanted to allow user to not agree to that (with Shih Tzu flags). Question is: how can I test (with Rspec) if the FedbackMailer.new_feedback line gets executed?
account.users.each do |user|
return if (user.no_notifications || user.just_summary)
FeedbackMailer.new_feedback(account.id, feedback_id, user.id).deliver_later
end
You can use rspec-mocks.
mailer = instance_double
allow(FeedbackMailer).to receive(:new_feedback).with(account_id, feedback_id, user_id).and_return(mailer)
allow(mailer).to receive(:deliver_later)
## do stuff ##
expect(mailer).to have_received(:deliver_later)
You can also ignore the .with if you haven't the arguments to pass in that moment.
Another solution is to set a config config.action_mailer.delivery_method = :test and check if the delivery counts have changed.
expect {
## code that deliver the email
}.to change { ActionMailer::Base.deliveries.count }.by(1)
Let's say your logic is encapsulated in following method in class MyClass
class MyClass
def my_method
account.users.each do |user|
return if (user.no_notifications || user.just_summary)
FeedbackMailer.new_feedback(account.id, feedback_id, user.id).deliver_later
end
end
end
RSpec.describe MyClass, type: :model do
context "#my_method" do
it "should send new feedback" do
user_obj = create_user
expect(user_obj.no_notifications).to be_falsey
#OR
#expect(user_obj.just_summary).to be_falsey
account_obj = create_account
account_obj.users << user_obj
expect(account_obj.users).to include(user_obj)
expect(FeedbackMailer).to receive(:new).with(account_obj.id, feedback_id, user_obj.id)
# OR in case you don't have feedback_id then you can use
# expect(FeedbackMailer).to receive(:new).with(account_obj.id, kind_of(Numeric), user_obj.id)
# You should also setup expectation here that `FeedbackMailer` gets enqueued to ensure
# that your method also gets invoked and the job also gets enqueued.
subject.my_method
end
end
end
Hope that helps. Thanks.
I have a rails method which allows a user to submit a review and to counterparty, it sends an email using the delayed jobs.
def update_review
#review.add_review_content(review_params)
ReviewMailer.delay.review_posted(#review.product_owner, params[:id])
end
And I am trying to add a rspec test for this to check if the mailer is delivered properly and to whom. The delayed jobs run immediately after they are created on test because I want other jobs like the job to update the product owners overall rating to be completed immediately.
So the email does get fired but how can I add a test for it?
EDIT: Adding current tests
My current tests are:
describe 'PUT #update the review' do
let(:attr) do
{ rating: 3.0, raw_review: 'Some example review here' }
end
before(:each) do
#review = FactoryBot.create :review
put :update, id: #review.id, review: attr
end
it 'creates a job' do
ActiveJob::Base.queue_adapter = :test
expect {
AdminMailer.review_posted(#coach, #review.id).deliver_later
}.to have_enqueued_job
end
it { should respond_with 200 }
end
This does test that the mailer works properly but I want to test that it gets triggered properly in the method flow as well.
It sounds like what you want is to ensure that the update_review method enqueues a job to send the correct email to the correct recipient. Here's a simpler way to accomplish that:
describe 'PUT #update the review' do
let(:params) { { rating: rating, raw_review: raw_review } }
let(:rating) { 3.0 }
let(:raw_review) { 'Some example review here' }
let(:review) { FactoryBot.create(:review) }
let(:delayed_review_mailer) { instance_double(ReviewMailer) }
before do
# assuming this is how the controller finds the review...
allow(Review).to receive(:find).and_return(review)
# mock the method chain that enqueues the job to send the email
allow(ReviewMailer).to receive(:delay).and_return(delayed_review_mailer)
allow(delayed_review_mailer).to receive(:review_posted)
put :update, id: review.id review: params
end
it 'adds the review content to the review' do
review.reload
expect(review.rating).to eq(rating)
expect(review.raw_review).to eq(raw_review)
end
it 'sends a delayed email' do
expect(ReviewMailer).to have_received(:delay)
end
it 'sends a review posted email to the product owner' do
expect(delayed_review_mailer)
.to have_received(:review_posted)
.with(review.product_owner, review.id)
end
end
The reason I prefer this approach is that a) it could be done without touching the database at all (by swapping the factory for an instance double), and b) it doesn't try to test parts of Rails that were already tested by the folks who built Rails, like ActiveJob and ActionMailer. You can trust Rails' own unit tests for those classes.
I'm trying to test so a user cannot send a message to himself. Currently on my new message view, I have a select box which gives a selection of all the users in the system except for the current_user. Currently I only have a test which does not allow a user to select himself as the recipient from the select box:
it { should_not have_select(:receiver_id, :options => [user.name]) }
However, is this enough of a test? Do I need to test creating a new message, setting the :receiver_id to the current_user's id and check for it? If so, where would I put this spec, in the model or a request?
Edit (added a validation method in the Message Model, but my rspec passes even if I comment out the validate line):
Edit 2 (The test for the errors hash does not pass):
Message.rb:
validate :validate_sender_receiver
def validate_sender_receiver
if self.receiver_id == self.sender_id
errors.add(:receiver_id, "Cannot send message to self")
end
end
messages_spec.rb
describe "sending message to yourself" do
before do
#message = user.sent_messages.new(:receiver_id => user.id)
end
it "should not be valid" do
#message.should_not be_valid
end
it "should set the error hash" do
#message.errors.should include("Cannot send message to self")
end
end
If a user hacks your select and adds himself to the possible values you might end up with a message that you don't want. I don't know what your controller's action looks like but you should test that in the model and your model should reject the message if the receiver is the same as the sender.
I changed:
it "should set the error hash" do
#message.errors.should include("Cannot send message to self")
end
to:
it "should set the error hash" do
#message.errors.should have_key(:receiver_id)
end
And it now works out well, still don't understand why the first method doesn't work? Does the have_key just check to see if there is a key, but not if it's empty?
I want to test my Mailer model.
Letters should be sent to multiple recipients.
How to test that messages sent to multiple recipients?
I created spec file:
describe SubscribeMailer do
before(:each) do
(1..3).to_a.each do
FactoryGirl.create(:subscriber)
end
end
let(:post) { #user.posts.create(#attr)}
let(:mail) { SubscribeMailer.new_post(post) }
#Failure/Error for this test
it 'subscribers' do
mail.to.should == Subscriber.all.map(&:email)
end
end
But I don't know how to write the test.
In addition to #Trevoke answer, I'd tell you to have a look at this excellent Railscast.
In test environment, ActionMailer.deliveries is an array of mail objects that would otherwise have been sent.
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.
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\r\nTo: 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
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.
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:
ActionMailer Guide/Testing
Testing Guide/ActionMailer
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!