How do you trigger the evaluation of a mail method using RSpec? - ruby-on-rails

class MyMailer < BaseMailer
def send_login_prompt
User.reset_login
# ...
end
end
describe MyMailer do
context "when sending a login prompt" do
it "should reset the user's password" do
expect(User).to receive(:reset_login)
MyMailer.send_login_prompt
end
end
end
The incarnation of the test above fails
In the example above we've got a mailer method that triggers a user's password to be
reset. We want to test for that. However when you run the test, .send_login_prompt
does not get called.
In order to actually trigger the code to be evaluated you have to add a line to
inspect the mail object:
Test that works
it "should reset the user's password" do
expect(User).to receive(:reset_login)
mail = MyMailer.send_login_prompt
mail.body # after this line is added the mail is built and the test passes
end
Why is the method not called before and what's the most correct way to prompt the
method to be evaluated rather than the ad-hoc route of calling mail.body?

The mail is not delivered until you access it's content or you can one of the .deliver functions.
This should get the job done nicely
mail = MyMailer.send_login_prompt.deliver_now

Related

How can I test an asyncronous mailer in a rails RSpec feature test?

I am trying to write a feature test where a user leaves a comment and then a notification is sent out via a rails mailer. Everything is working here if I change my mailer call to use .deliver_now but I don't want that in production. I need to be able to test the asynchronous mail delivery or even just force the mailer to deliver now in the test scenario.
login_as campaign_owner
visit campaign_path campaign
fill_in 'Comment', with: 'Foo'
click_on 'Submit'
expect(page).to have_content I18n.t('comments.create.success')
expect(campaign.comments.count).to eq 1
expect(UserMailer.deliveries.count { |d| d.to == [campaign_watcher.email]}).to eq 1
You could always just change the behaviour of your mail delivery by testing for what environment you are running in.
eg,
if Rails.env.test?
UserMailer.mail_template().deliver_now
else
UserMailer.mail_template().deliver_later
end
Though it's questionable what value your test is actually giving you then

Writing tests for user authentication with rspec

I am currently trying to write unit tests for user authentication in rails and I keep running into a problem.
I am trying to test the following method:
def reset_session_token!
self.session_token = User.generate_session_token
self.save!
self.session_token
end
with the following unit test:
let(:valid_user) { User.new(user_name: 'Name', password: 'abcdefghijkl')}
it "should set the session_token" do
valid_user.reset_session_token!
expect(valid_user.session_token).not_to be_nil
end
but the test fails with the error Validation failed: User name has already been taken. I suspect that it is because reset_session_token! calls save on the user instance but this is necessary for the method to work properly. How can I get around this?
Thanks to Oleg Sobchuk's comment I was able to figure out how to deal with the save! method.
Since stub has been deprecated I used allow_any_instance_of with receive instead. Here is what I did:
it "should set the session_token" do
allow_any_instance_of(User).to receive(:save!).and_return(nil)
valid_user.reset_session_token!
expect(valid_user.session_token).not_to be_nil
end

Test presence of ActiveRecord callbacks

How can you test the presence of a callback in your model, specifically one that's triggered by creating a record, such as after_create or after_commit on: :create?
Here's an example callback with the (empty) method that it calls.
# app/models/inbound_email.rb
class InboundEmail < ActiveRecord::Base
after_commit :notify_if_spam, on: :create
def notify_if_spam; end
end
Here's the pending spec, using RSpec 3.
# spec/models/inbound_email_spec.rb
describe InboundEmail do
describe "#notify_if_spam" do
it "is called after new record is created"
end
end
Using a message expectation to test that the method is called seems like the way to go.
For example:
expect(FactoryGirl.create(:inbound_email)).to receive(:notify_if_spam)
But that doesn't work. Another way is to test that when a record is created, something inside the called method happens (e.g. email sent, message logged). That implies that the method did get called and therefore the callback is present. However, I find that a sloppy solution since you're really testing something else (e.g. email sent, message logged) so I'm not looking for solutions like that.
I think Frederick Cheung is right. This should work. The problem with your example is that the callback has already been called before the expectation has been set.
describe InboundEmail do
describe "#notify_if_spam" do
it "is called after new record is created" do
ie = FactoryGirl.build(:inbound_email)
expect(ie).to receive(:notify_if_spam)
ie.save!
end
end
end

Rails rspec testing a user sending a message to himself/herself

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?

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.
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!

Resources