Reputation: 1278
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 [email protected] (62ms)
Date: Tue, 29 Jan 2013 13:53:48 -0500
From: Review Studio <[email protected]>
To: [email protected]
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)
Upvotes: 8
Views: 5951
Reputation: 2044
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
Upvotes: 1
Reputation: 2508
In my case config.action_mailer.perform_deliveries
was false
for test environments.
Upvotes: 3
Reputation: 1278
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.
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.
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.
Upvotes: 7
Reputation: 5511
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.
Upvotes: 0
Reputation: 1
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
Upvotes: 0
Reputation: 15838
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)
Upvotes: 6