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
Related
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
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
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
I am using sidekiq to queue a job to validate the input of a field on a model (a link) and visit the link to make sure it doesn't return a 404 if it passes a regex validation.
If the regex validation passes and the link does not return 404 I assign another attribute to the same model but on a different field.
The worker is trigger by calling a method, the method is triggered after an after_commit, on: :update callback
The method only triggers the worker if 'previous_changes['json object I am using to make changes']' is true. This is so the worker won't continue to be called over and over again.
All of this works fine in development, confirmed with multiple use cases by QA'ing it myself
I am trying to write a test in MiniTest to ensure that a worker gets queued when the model's json field is changed but a worker is not being queued and for the life of me I can't figure out why.
I am testing the expectations for the worker being queued with:
assert_difference 'WorkerClass.jobs.size' do
site = sites(:site_from_fixtures)
site.attribute_that_triggers_change = { "random" => "json_object" }
site.save
end
The test returns: "Expected: 1, Actual: 0"
Any input would be greatly appreciated!
This issue was fixed by installing the test_after_commit gem.
Apparently after_commit's don't fire in tests unless you either use that gem or specify the necessary changes in a test_helper. I opted for the gem option just to keep things a little easier.
I'm trying to use delayed_job to send emails from an input form. In my view i have replaced the line
Emailer.deliver_signup(#usercontact)
with
Emailer.send_later(:deliver_signup, #usercontact)
but when i run the job with rake jobs:work, i get: undefined method 'deliver_signup' for "CLASS:Emailer":String
What am I doing wrong? (Note that the code works without delayed_job)
Delayed job mailer might help rather than trying to implement the details yourself.