Rspec: ActionMailer::Base.deliveries is always empty - ruby-on-rails

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.

Related

How to fix a flaky Capybara test for the Devise password reset flow

I have a flaky system test in Rails that I can't seem to fix. Each time this test initially started failing I kept adding a few seconds to capybara's using_wait_time method and the test would pass for a while. Usually this test passes in isolation and then fails when I run my entire test suite. Then I bump up the wait time and it will pass for a while when running the entire test suite until I change something else in my app that for whatever reason breaks this specific test.
At first I thought, ok I just need to wait for the browser, this is a system test, but now this wait time has started to creep up to an absurd level. (Side note: a similar thing is happening to a system test for entering a search term into the search bar and waiting for a response.) Is there a better way to test this or telling Capybara to wait for the browser the only way? I know that I can trust Devise and don't need to unit test Devise's password reset mailer, but I would like to test the user's flow through the password reset process.
Here's the flaky test:
require 'application_system_test_case'
class UserPasswordResetTest < ApplicationSystemTestCase
def setup
ActionMailer::Base.deliveries.clear
#user = users(:george)
end
test "should allow you to request a password reset email" do
visit login_path
click_link "Forgot your password?"
assert_current_path "/users/password/new"
using_wait_time(30) do
fill_in "user[email]", with: #user.email
end
click_button "Request password reset"
using_wait_time(25) do
assert_text "You will receive an email with instructions on how to reset your password in a few minutes."
end
end
end
And here are my test helpers:
test/application_system_test_helper.rb
test/test_helper.rb
What is a better way to test this password reset flow that isn't as flaky?
If the test is passing with the extended waits, then you need to look at your test log to see why your controller actions are taking so long to render (Capybara is just waiting for that stuff to appear in the browser, and continues as soon as it does). If the tests aren't reliably passing with extended waits then you'll need to provide details on exactly what errors are being returned.
Also note that when you're only adjusting the maximum wait time for a single call you don't need to use the using_wait_time method, you can just pass wait to the method you're calling
fill_in "user[email]", with: #user.email, wait: 30

Rspec feature test with sidekiq mail delivery, how to wait for mail observer?

when I send a mail through deliver_later it is managed by sidekiq, and then my registered mail observer is triggered.
I have a Capybara test that checks states changed inside observer code, but it fails randomly if observer is not executed right after the clicks, and the expectation doesn't work correctly.
Example:
# spec
scenario 'Test that fails randomly' do
click_link "Go!"
# MyModel#done is a boolean attribute, so we have #done? method availiable
expect(MyModel.first.done?).to be true
end
# The controller that manages the Go! link, triggers a mailer.
# After the mailer, this is executed.
# Registered observer
def delivered_mail(mail)
email = Email.find_by_message_id mail.message_id
email.user.update_attributes done: true
end
Fun fact: If I execute this scenario isolated, the test will always pass.
If I execute the test suite completely, the test will 9:1 fail:pass more or less. ¯_(ツ)_/¯
Tried putting this in rails_helper:
require 'sidekiq/testing'
RSpec.configure do |config|
Sidekiq::Testing.inline!
end
And also putting Sidekiq::Testing.inline! in the very first line of the scenario block... nothing. The same fun fact.
Update:
Added database_cleaner gem, and now it fails everytime.
Actions in Capybara (click_link, etc) know nothing about any behaviors they trigger. Because of this there is no guarantee as to what the app will have done after your click_link line returns, other than the link will have been clicked, and the browser will have started to perform whatever that action triggers. Your test then immediately checks 'MyModel.first.done?` while the browser could still be submitting a request (This is one reason why directly checking database records in feature tests is generally frowned upon).
The solution to that (and end up with tests that will work across multiple drivers reliably is to check for a visual change on the page that indicates the action has completed. You also need to set up ActiveJob properly for testing so you can make sure the jobs are executed. To do this you will need to include ActiveJob::TestHelper, which can be done in your RSpec config or individual scenarios, and you will need to make sure ActiveJob::Base.queue_adapter = :test is set (can be done in config/environment/tests.rb file if wanted). Then assuming your app shows a message "Mail sent!" on screen when the action has completed you would do
include ActiveJob::TestHelper
ActiveJob::Base.queue_adapater = :test
...
perform_enqueued_jobs do
click_link "Go!"
expect(page).to have_text('Mail sent!') # This will wait for the message to appear, which guarantees the action has completed and enqueued the job
end # when this returns any jobs enqueued during the block will have been executed
expect(MyModel.first.done?).to be true

Capybara webkit doesn't pass params from angular

I am trying to port a selenium test suite to capybara-webkit. The Rails app has an angular app embedded in the rails views and is not behaving as expected with webkit.
A test like this:
require 'spec_helper'
feature 'Editing company profiles' do
before do
#user = create(:employee)
#company = Company.find(#user.employer.id)
sign_in_as! #user
end
scenario 'successfully', js: true do
click_link 'Dashboard'
click_link #company.name
click_button 'Edit'
fill_in 'company_name', with: 'new name'
click_button 'Save'
expect(page).to have_content "Your company profile has been updated!"
end
end
Will pass without issue in selenium, but with webkit I get the error
Failure/Error: Unable to find matching line from backtrace
ActionController::ParameterMissing:
param is missing or the value is empty: company
# ./app/controllers/api/v1/companies_controller.rb:23:in `company_params'
# ./app/controllers/api/v1/companies_controller.rb:10:in `update'
The trace is missing, maybe because it's from angular land, but the error is reporting that no params are coming from the client. I've tried the capybara-angular gem, but it has not helped. I've also tried saving the page with capybara and nothing looks out of place there, are there any ways to access the PATCH request inside of webkit that's being generated in this test? I've also gotten similar errors with poltergeist.
Has anyone setup headless rspec testing with angular + rails? Any tips on how to debug why data isn't being sent over from the client?
Without seeing all of your code, this feels like it could be a problem associated with a known issue in the capybara-webkit gem is unable to pass entity bodies to the server.
I suspect that the update request is being sent as a PATCH request (which is appropriate), but the issue with the gem results in failure for your tests.
A workaround to your problem is to change the method of the request to PUT or POST, the issue linked above shows some options. You will be able to get your test to pass, but it's up to you to decide if changing the request type is worth getting your test to pass.
Note: In practice it may not matter if you don't actually use PATCH, as you could technically use (some of) the other http methods interchangeably -- but use caution as there are reasons to use a specific http method for a given situation. See this rubyonrails.org post from a few years ago for some details.

Stubbing time in a Capybara test

I have a page that creates a snapshot of a document. That document is saved with the title being a timestamp (September 27, 2014 at 4:01:10 pm) for example. I am writing a test for this page and want to stub time so that it doesn't change.
What I have at the moment is Time.stubs(:now).returns(Time.parse("2014-1-2 11:00:00")) but when I do that I get an error message saying:
Capybara::FrozenInTime: time appears to be frozen, Capybara does not work with libraries which freeze time, consider using time travelling instead
What is the best way to stub out time here?
Rails now includes support for time travel directly, e.g.:
feature 'Time travel verifier' do
include ActiveSupport::Testing::TimeHelpers
scenario 'works in the past' do
travel_to(1.day.ago) do
visit time_travel_verification_path
expect(page).to have_content('WOAH Time Travel!')
end
end
end
I'm reposting here my comment as an answer.
There is the timecop gem https://github.com/travisjeffery/timecop
It allows you to do things like the following:
describe "some set of tests to mock" do
before do
Timecop.freeze(Time.local(1990))
end
after do
Timecop.return
end
it "should do blah blah blah" {}
end
which will make the tests run as if it was 1990-1-1 and then return back to the current time.

Why are emails not getting in the ActionMailer::Base.deliveries table?

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.

Resources