Why are emails not getting in the ActionMailer::Base.deliveries table? - ruby-on-rails

Currently in my development environment I am not seeing anything in the ActionMailer::Base.deliveries table after mail is used. Is there a setting I am missing?
config/environments/development.rb: config.action_mailer.delivery_method = :test.
app/mailers/notifier.rb
def send_email(user)
mail(:to => user.email, :subject => "Welcome to our website!")
end
These are the tests I'm running:
When /^I signup$/ do
visit signup_path
#user = FactoryGirl.build(:user)
fill_in "user_email", :with => #user.email
fill_in "user_password", :with => #user.password
click_button "Join now"
end
Then /^I should receive a confirmation email$/ do
email = ActionMailer::Base.deliveries.last
email.subject.should == "Welcome to our website!"
end
Right now I get the following error for the I should receive a confirmation email step:
undefined method subject for nil:NilClass (NoMethodError)
Thanks!

Another possibility might be that in config/environments/test.rb you have
config.action_mailer.delivery_method = :test
but you have set
config.action_mailer.perform_deliveries = false
so changing to
config.action_mailer.perform_deliveries = true
made it work again in my case.

Old question, I know, but a couple things come to mind:
You talk about the configuration of your development environment (config/environments/development.rb), but it's your test that isn't working. Check config/environments/test.rb and maybe config\application.rb.
I actually wanted to see the email array in my development environment, but it always returned nil in the console. I finally realized that because the array is just a memory object, not in the database, the console (rails c) can't see what's going on in the server (rails s). When I added <%= ActionMailer::Base.deliveries.count %> to a view, I could immediately see the array getting populated in development.
Another "I can't see that process from the console" situation arises if you are using a separate thread or task for sending emails. With delayed_job, I found this answer suggesting you can look at the job (not the actual email) using Delayed::Job.last.handler, which does look at the database so works across processes. However this only works while the job is still in the database, i.e. before a worker has processed it.

The error you are getting says that email is nil. In other words, the deliveries.last method is returning nil because there are no emails in it.
There could be a number of reasons... I'm assuming that you are using cucumber as the testing mechanism?
1. Verify that 'click_button' submits
You could try to put puts or log statements. Make sure that when you run the cucumber feature, it actually prints. Sometimes these things don't work because the front-end logic doesn't work and the test is failing correctly. In other words, the controller endpoint you are expecting to be triggered isn't being triggered.
2. Verify that 'send_email' is right - using a unit test
Next, verify that you are actually sending email. Write a unit test to verify this works. This should be quick and easy to get going. Plenty of articles out there.
Lastly, not really related, but you might want to roll with email-spec which should provide some nice testing methods so you don't have to reinvent the wheel.

After making the changes answered by #Dominik Steiner, if it still didn't work ,
another possibility might be that in config/initializers/mailer.rb, you should have :
ActionMailer::Base.delivery_method = :test
Above was missing in my case and it worked now.

Related

Devise login acceptance test with capybara

There are already a few questions on stackoverflow with possible solutions, but none of them seemed to solve my problem, thus I'm asking another one, in hope it's specific enough to not be closed as duplicate.
I am trying to test a customized login form that uses devise. The environment consists of
rails 6.0
rspec-rails 3.9
capybara 3.31
selenium-webdriver 3.142.7
database_cleaner 1.8
The following spec is failing due to a 401 Unauthorized response, rendering the login form again with an error message that the credentials were wrong.
require 'rails_helper'
feature 'Logging in' do
background do
FactoryBot.create :user, email: 'john.doe#example.com',
first_name: 'John',
password: 'password',
password_confirmation: 'password'
end
scenario 'with correct credentials' do
visit new_user_session_path
within('#user-sessions--new') do
find('#user_email').fill_in with: 'john.doe#example.com'
find('#user_password').fill_in with: 'password'
end
find('input[name="commit"]').click
expect(page).to have_content 'John'
end
end
Checking the test.log after temporarily disabling the parameter filters for logging, I can see that the password has been submitted properly, and the user is being created before the login attempt happens, yet the authentication fails. Entering an interactive debugging session and trying to log in manually inside the capybara's spun up browser, the login also fails. The email/password login works in development mode when creating the same user through the console, though. Right now, no other devise features that could impact the login behavior (like confirmable) are used.
Most of the questions I found so far advise the following points:
Disable transactional fixtures: My rails_helper.rb already contains this;
RSpec.configure do |config|
config.use_transactional_fixtures = false
end
Configure Database Cleaner: This was suggested in Failing to test Devise with Capybara and was also already the case before I started to implement capybara specs;
RSpec.configure do |config|
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(
:truncation,
except: %w[ar_internal_metadata schema_migrations]
)
end
config.before :each do
DatabaseCleaner.start
end
config.after :each do
DatabaseCleaner.clean
end
end
Right now I have no other Idea why this scenario could be failing except for maybe the environment in which the tests are run hot having a proper database connection. If someone has an idea that helps, I'd be happy to hear it.
When using Rails > 5.0 it safely shares the database connection between the tests and the app under test Capybara starts. Because of that you don't need database cleaner, and you should be using transactional tests for the speed and isolation benefits. Start by removing all references to database cleaner from your project and re-enabling transactional fixtures (the default). If that doesn't solve your issue then there's a couple of other potential issues.
Your factory isn't creating a valid user - Use the FactoryBot.lint functionality to verify all your factories produce valid objects before your tests - https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md#linting-factories
You have a JS error on your page that is causing issues. In development mode each JS asset is served separately which means an error in one doesn't affect the others. In test mode, however, the assets are all concatenated which means an error in one can cause JS in other assets not to be run. Check your browser console for any JS errors and fix them.
If none of that helps please add relevant portions of your test.log to your question
Note: You're writing your tests more verbosely than they need to be. If locating fields to fill_in by id you can just pass the id to fill_in and not need to use separate find calls for every element, you should also use the more semantic click_button when the element you're attempting to click qualifies as a button
within('#user-sessions--new') do
fill_in 'user_email', with: 'john.doe#example.com'
fill_in 'user_password', with: 'password'
end
click_button('commit')
expect(page).to have_content 'John'

Is there any way to prevent Rails autocleaning class variables when testing emails?

I testing a Rails application that sends emails in some situations. It's an API.
For the testing, I'm using the Airborne gem, which makes API testing pretty easy. All went correct except when I had to test the email deliveries. I tried the following:
it "blah" do
//Code that makes my API send an email
puts ActionMailer::Base.deliveries.inspect
end
But deliveries array is always empty. I also tried with Emails.deliveries.inspect. Emails is my custom Mailer that inherits ActionMailer::Base.
I ended reading the API documentation of ActionMailer and met the interceptor concept. Interceptors doesn't work in :test delivery method so I switched to :smtp. In fact, the emails are being sent correctly, but I can not access them on the tests to make expectations.
My interceptor code is this right now
initializers/email_interceptor.rb
class EmailInterceptor
##msgs = []
def self.delivering_email(message)
puts message
//Rails.logger.debug "Email being sent: " + message.to_s
##msgs << message
Rails.logger.debug "Actual messages array: #{##msgs}"
end
def self.msgs
##msgs
end
end
ActionMailer::Base.register_interceptor(EmailInterceptor)
All OK. The debug messages print the array being populated correctly. But the variable is cleaned before my test statement is executed.
EDIT: The code above is executed when I run my test suite. But the variable is empty accessed from the test itself.
//test code
puts EmailInterceptor.msgs.inspect
=> []
Is there any way to prevent this behavior?
You may have config.action_mailer.perform_deliveries = false in your test.rb config. It seems like you should really be using config.action_mailer.delivery_method = :test since this will allow ActionMailer::Base.deliveries to be populated, which makes for easier and more reliable testing. Do you really need interceptors for your tests?

Rspec is not creating database records accessible from poltergeist

I'm having terrible trouble getting Poltergeist and RSpec to play together nicely.
I've written the following test:
it "allows the trainer to view a runner" do
visit '/'
all(:xpath,'//a[#id="get-started"]').first.click
fill_in :name, with: "New Admin"
fill_in :email, with: "admin#test.org"
fill_in :password, with: "letmein"
fill_in :password_confirmation, with: "letmein"
all(:xpath,'//input[#id="get-started-submit"]').first.click
#runner_1 = FactoryGirl.create(:runner, name: "Axel", email: "axel#test.org")
visit '/runners/axel'
debugger
Effectively, what the above is doing is registering 'New Admin' with the password, 'letmein', then trying to view the runner profile page for 'Axel'.
Where the debugger interrupts, I can see that #runner_1 (Axel) has been created:
Runner.friendly.find('axel')
>> #<Runner id: 2, email: "axel.manzano#hotmail.fr",........>
However, when trying to visit '/runners/axel', Poltergeist reports:
ActiveRecord::RecordNotFound
It's not an issue with routes, or anything like that.
Having explored this bug a little further, in fact, it seems anything created in the test file doesn't actually get set up in the environment that Poltergeist is accessing.
I can't seem to understand why. Any help greatly appreciated.
Chances are, you are using "transactional fixtures" in rspec. This means that each test run in a database transaction, which is rolled back at the end of test, so that each test has a clean database.
Other threads/programs can not see what is going on in the transaction. Poltergeist runs the server in the separate thread, which means that it can not see anything that is written to the database in rspec (although it can be accessed directly from the rspec code).
There is a description of this phenomenon on the capybara homepage. The solution is to disable the transactional feature in rspec-rails and use something like DatabaseCleaner to reset the database after a test.
This will work, but unfortunately truncating or deleting the database contents is somewhat slower than they would with the transactional approach - this is why the tranasactions are the default in the first place.

Rspec: ActionMailer::Base.deliveries is always empty

I've written an RSpec integration test. According to test.log, I can see that it has sent an email, but when I try to access the email using ActionMailer::Base.deliveries, it always shows it is empty.
I have read the other similar questions and tried everything. I'm stumped.
Here is the code using Capybara/RSpec:
it "should notify owner" do
puts "Delivery method: #{ActionMailer::Base.delivery_method.inspect}"
# This returns: ":test", which is correct
# Get other guests to do the review
#review = Review.last
share_link = "http://#{#account.subdomain}.cozimo.local:#{Capybara.server_port}/review/#{#review.slug}"
visit(share_link)
fill_in "email", :with => #user1.email
fill_in "password", :with => "foobar"
click_button "Start Review"
# Add a comment
click_on "ice-global-note-button"
find(:css, "#ice-global-note-panel > textarea.ui-corner-all").set "Foo"
click_on "ice-global-note-submit-button"
# Test is failing here. WTF? The array should not be empty
ActionMailer::Base.deliveries.empty?.should be_false
# Check that a notification email was sent to the owner
open_email(#owner.email)
current_email.should have_content "Hi #{#owner.first_name}"
end
As you can see above, config.action_mailer.delivery_method = :test
In test.log, it shows that the email really is sent!
Sent mail to somebody1#example.com (62ms)
Date: Tue, 29 Jan 2013 13:53:48 -0500
From: Review Studio <support#cozimo.com>
To: somebody1#example.com
Message-ID: <51081abcd9fbf_5bd23fef87b264a08066#Leonards-MacBook-Pro.local.mail>
Subject: Notes added for Test
Mime-Version: 1.0
Content-Type: text/plain;
charset=UTF-8
Content-Transfer-Encoding: 7bit
Hi Bruce,
Just letting you know that you have new notes added to the review:
Project: Test
Description: Test description
URL: http://ballistiq.cozimo.local:3000/review/625682740
Thanks,
Review Studio
Completed 200 OK in 170ms (Views: 0.2ms | ActiveRecord: 4.3ms)
This is an integration test using Capybara and Selenium. Therefore, you have to wait for the application to actually send the mail before checking that it has sent it.
Note - this solves the problem but is generally bad practice
Add a sleep 1 to tell rspec to wait after triggering the send mail event. It then resumes by checking the ActionMailer::Base.deliveries array and passed.
As mentioned, this is generally bad practice because it slows down tests.
Better way
Integration test shouldn't test the mail is sent at all. Tests should be divided up into clear responsibilities for the class being tested. Therefore, we'd structure the tests differently so that we only test for the mail being sent in another class (a controller or resource test). We could also use expectations to check that the call to the mail method was actually made though it's possible that we'd still get timing issues.
You should never use sleep or those time consuming methods on the specs, i't will only slow down your specs!
Try using an expectation on your mailer, at the beginning of your test add something like
mail = mock(mail)
mail.should_receive(:deliver)
YourMailer.should_receive(:your_method).once.and_return(mail)
that way you don't have to wait and you are actually testing what you have to test (that the code creates and delivers the mail) and not the mailer code (you only call deliver on a mail object, the actual delivery is a job of the ActionMailer tests and you have nothing to do with it on your application, you should just trust that calling those method works)
In my case config.action_mailer.perform_deliveries was false for test environments.
I had the same problem, and finally I found an easy solution, not sure if is the best way: All appreciations or corrections are welcome.
First of all adding this line in environtments/test.rb
config.active_job.queue_adapter = :inline, this make execute a "deliver_now" even though we put in our code deliver_later
In the test add an expectation for the email message (I allways show a message telling that an email was sent, I guess that you too), for example:
expect(page).to have_content("We have sent a confirmation e-mail")
expect(ActionMailer::Base.deliveries.count).to eq(1)
This first expectation will wait untill 2 seconds (is the default timeout, eventhough is configurable via Capybara.default_wait_time = 2s).
So, in my case, putting this expectation and adding this config line in config/environments/test.rb is enough for passing the test correctly
You could also use this wait_for_ajax helper instead of sleep for your javascript POST. It will make Capybara wait for the ajax to finish.
module WaitForAjax
def wait_for_ajax
Timeout.timeout(Capybara.default_wait_time) do
loop until finished_all_ajax_requests?
end
end
def finished_all_ajax_requests?
page.evaluate_script('jQuery.active').zero?
end
end
RSpec.configure do |config|
config.include WaitForAjax, type: :feature
end
Source:
https://robots.thoughtbot.com/automatically-wait-for-ajax-with-capybara
I think in general you want to avoid sleep() in your tests. If you're doing an integration test with Capybara you just need to use a method that waits.
For example, this test fails intermittently (valid_step_7 sends an email):
it "should get past step 7 and send an email" do
valid_step_5_no_children
valid_step_7
expect(ActionMailer::Base.deliveries.count).to eq(1)
expect(page).to have_content "I AM JOHN DOE"
end
This is because it's finishing the last step (valid_step_7) and instantly checking 'ActionMailer::Base.deliveries.count' to see if it's equal to 1, but it hasn't necessarily hasn't had time to populate the deliveries array (from my debugging anyways).
I haven't had random failures since I flipped my test to first check the content on the next page, give the time for ActionMailer::Base.deliveries to be populated and perform the check:
it "should get past step 7 and send an email" do
valid_step_5_no_children
valid_step_7
expect(page).to have_content "I AM JOHN DOE"
expect(ActionMailer::Base.deliveries.count).to eq(1)
end
I suspect sleep(1) will work, but I think you're forcing a 1s sleep when it's not needed.

Sending emails in test mode with ActionMailer in rails 3

I am having a slightly odd problem with sending mail in test mode with Rails 3
It seems that my mailers are not returning anything. For example I have a mailer called UserMailer. Users can make changes that require approval in the app so this has a method called changes_approved that should send the user an email notifying them that their changes have been approved.class
UserMailer < ActionMailer::Base
default :from => "from#example.com"
def changes_approved(user, page)
#user = user
#page = page
mail(:to => user.email, :subject => "Your changes have been approved")
end
end
In my controller I have the following line
UserMailer.changes_approved(#page_revision.created_by, #page_revision.page).deliver
However my tests fail at this point with the error:
undefined method `deliver' for nil:NilClass
When I trigger the same actions on the development site tho (http://localhost:3000 through a browser), the emails are sent out correctly and everything works quite happily
And to add further confusion, I am using devise for authentication and the emails for that seem to be working correctly both in test and development modes. Certainly I am not getting this same error and according to my email-spec tests, everythings working
So this leads me to believe that I have a problem with my mailers rather than my test mail config per se but I have no idea what. Any suggestions would be much appreciated
Thanks
I used https://gist.github.com/1031144
to convert
# Rails 2 method:
UserMailer.should_receive(:deliver_signup)
to
# Cumbersome Rails 3 method:
mailer = mock
mailer.should_receive(:deliver)
UserMailer.should_receive(:signup).and_return(mailer)
I had a similar problem - probably the UserMailer.changes_approved method is being replaced with a mock method, which returns nil (I wasn't using shoulda for that test, but that's my best guess).
My code looked like this (modified to use your example):
UserMailer.expects(:changes_approved).once
I fixed it with an additional stub:
#mailer = stub(:deliver)
UserMailer.expects(:changes_approved).once.returns(#mailer)
The nil is now replaced with #mailer.
To test the delayed action mailer we need to first change the configuration of delayed_job (in config/initializers/delayed_job_config.rb) to
Delayed::Worker.delay_jobs = !Rails.env.test?
and in your tests the expectation should be set to
mock_mail = mock(:mail)
mock_mail.should_receive(:deliver)
UserMailer.should_receive(:changes_approved).with(user, page).and_return(mock_mail)
Well I have found the answer,
it looks like the problem was in the way I was testing these mailers. In each of the controller tests I had a line similar to
UserMailer.should_receive(:changes_approved).with(user, page)
Whilst this test was passing fine, it appeared to break the mailer itself. I have removed this line from the tests and now they pass ok. Subsequent tests against ActionMailer::Base.deliveries.last to check the details of the sent email are correct appear to be ok so I am happy that this line is not neccessary.
If anyone has an explanation as to why this breaks tho, I would be interested to find out
Thanks anyways

Resources