Q:How to test ActionMailer deliver_later with Turnip,capybara and Gherkin - ruby-on-rails

def deliver_mail
ServiceMailer.activation().deliver_later
end
deliver_mail method is called from some controller.
I want to test like below - feature test using cucumber and capybara.
step 'push next button' do
find("input.submit").click
end
Feature: Sending a mail to user
Scenario: mail to a user
When I push next button
Then mail should be sent to a user
actually, when 'push next button' is pushed, mail is sent by deliver_mail method.
when I use deliver_now instead of deliver_later, I can test the code above.
but after I change deliver_now to deliver_later, I can not test.
so I referenced below.
http://chriswarren.github.io/rpsec/testing/2015/03/13/testing-emails-and-active-job-in-rspec-feature-tests.html
I tried to include 'ActiveJob::TestHelper' like 'include ActiveJob::TestHelper' in spec file.
and i modified step file like this.
step 'push next button' do
 perform_enqueued_jobs do
find("input.submit").click
 end
end
but still doesn't work.
any hint and advice please.

I am using Delayed::Job for sending emails in a background, so when I test sending emails, I have two separate kinds of tests:
1) rspec - to check email body (in general, the method inside mailer) and sending the email itself
2) cucumber - to check if the email job was queued
In cucumber, I have the following code:
Then(/^A welcome email will be sent$/) do
expect(Delayed::Job.count).to eq(1)
end
I think there is no need to check anything else from the cucumber.

I would recommend not delaying jobs in test, and then checking the results of whatever DJ was supposed to run.
# rails_helper.rb
Delayed::Worker.delay_jobs = false

Related

Rake test equivalent for ActionMailer::TestHelper?

We have recently migrated from sending emails with deliver_now to deliver_later. So that queued emails aren't lost when the system restarts, we implement this with Sidekiq.
When we used deliver_now, our Rake tests could test the sending of an email with
assert_equal 1, ActionMailer::Base.deliveries.count
For Rspec there is the assert_enqueued_emails method to test whether or not emails are queued. Is there an equivalent for Rake test?
You can use the have_enqueued_mail matcher (documentation). Then your spec would use something like the following.
have_enqueued_mail(MyMailer, :my_particular_email).once
The solution was quite simple, once I knew what to do:
Add the line include ActionMailer::TestHelper to the start of the test file
Execute the method perform_enqueued_jobs after all emails were queued.
After that, all the old tests worked fine.
See https://api.rubyonrails.org/classes/ActiveJob/TestHelper.html#method-i-perform_enqueued_jobs

Rails 6 & deliver_later doesn't affect ActionMailer::Base.deliveries

After upgrading to Rails 6 I am noticing that default mailer's .deliver_later is not working the same as in Rails 5.
Configuration:
config.active_job.queue_adapter = :inline
When running Mailer.register_email(...).deliver_later - nothing is stored in ActionMailer::Base.deliveries. This array gets filled if I run perform_enqueued_jobs - it seams like queue_adapter = :inline doesn't work the way I expect it to work.
If I run Mailer.send(...).deliver_now then ActionMailer::Base.deliveries has proper value in it.
Any idea why this is happening and how to solve this?
I had same problem in my tests. A search on the Internet yielded nothing, so I started experimenting.
I tried wrapping the call method of sending mail in
assert_emails 1 do
Mailer.register_email(...).deliver_later
end
After that, ActionMailer::Base.deliveries populated correctly.
If the exact number of emails could easily change, this is another option:
assert_changes 'enqueued_jobs.size' do
# Some code that sends email with deliver_later
end
This allows you to test that emails were sent but it disregards the exact number (which is a limitation of the asserts_emails method - other than this, the asserts_emails method is great).
I found that the enqueued_jobs method is very helpful in testing anything background jobs, including deliver_later
NOTE: the above example only checks that the enqueued jobs list was changed. If you want to be more specific and check that the queue was changed with emails, you should do this:
assert_changes 'enqueued_jobs.select {|job| job["job_class"] == "ActionMailer::MailDeliveryJob"}.size' do
# Some code that sends email with deliver_later
end
The issue
The issue lies in two new lines of code added to Rails 6 (line 1 & line 2),
where basically, the callback before_setup defined here (in RSpec) and here (in Minitest) gets overridden (by this), thus forcing the queue_adapter to be the test adapter instead of the one defined by config.active_job.queue_adapter.
Workaround
So in order to use the queue_adapter defined by config.active_job.queue_adapter and therefore restore the Rails 5 behaviour we can do something like the below.
# spec/support/active_job/test_helper.rb
module ActiveJob
module TestHelper
def before_setup
super
end
end
end

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

ActionMailer::Base.deliveries empty when using delayed_job

I'm using delayed_job to send emails in a Rails app and I'd like to test the email sending locally. Normally what I do is just set the mailer config to :test and then take a peek at ActionMailer::Base.deliveries, but the problem is when I call MyMailer.delay.some_email instead of MyMailer.some_email.deliver, the email never gets added to deliveries. I assume it's because I'm not longer calling "deliver", but you're not supposed to call "deliver" when using delayed_job.
All my production emails work fine. It's only the testing ones that don't.
Thoughts?
The jobs are not being worked off in the test environment because there's no jobs runner working. You need to instead check that the job has been added to the queue.
# Call the function
assert_not_equal Delayed::Job.count, 0, "Jobs have been added to the queue"
You can then test the job works by working it off, like so:
Delayed::Worker.new.work_off 1
# Check the job has been done as expected

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