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

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

Related

ActiveJob could not find record in `test` environment

This issue only exists in test environment. Everything runs fine in development environment.
I am facing a strange issue after recently upgrading to Rails 5.0.0.1 from Rails 4.2.7.1. Everything was working fine before this upgrade.
In one of my models, I use ActiveJob to perform a task.
# webhook_invocation.rb
def schedule_invocation
WebhookRequestJob.perform_later(id)
end
def init
remember_webhook # No DB changes
init_errors_context # No DB changes
flow_step_invocation.implementation = self
flow_step_invocation.save!
return unless calculate_expressions # No DB changes
calculated! # An AASM event, with no callbacks
schedule_invocation
end
and in WebhookRequestJob#perform, I retrieve the object using the ID supplied
# webhook_request_job.rb
def perform(webhook_invocation_id)
invocation = WebhookInvocation.find_by(id: webhook_invocation_id)
invocation.run_request
end
The problem is that in the #perform, it cannot find the record (invocation becomes nil). I even tried putting p WebhookInvocation.all as the first line, but all it prints is an empty collection. On the other hand, if I try p WebhookInvocation.all in #schedule_invocation method, it properly prints out all the objects of WebhookInvocation.
There is no exception being raised, no lines of warnings either.
Edit 1:
I even tried passing the object directly to #perform_later i.e. WebhookRequestJob.perform_later(self), but the received object at #perform is nil.
Edit 2:
I noticed that there are some messages like Creating scope :fail. Overwriting existing method FlowStepInvocation.fail, caused by using AASM. I eliminated them by using create_scopes: false. But that still didn't solve the problem.
My guess from the info you supplied is that you have are calling the schedule_invocation method in a after_save or after_create callback. Since the callback is called, ActiveJob might start processing the job even before the object is actually persisted (before COMMIT is done). In this case your record will not show up in the database when job is processed and you will get an empty collection or nil.
To fix this change your callback to after_commit to make sure that the COMMIT action has happened before you queue the job.
It turns out that config.active_job.queue_adapter was set to :inline as default before Rails 5, but it is set to :async in Rails 5.
This made the specs to fail (don't know why). To resolve this, I put the following line in my config/environments/test.rb:
config.active_job.queue_adapter = :inline

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?

Sidekiq worker not getting queued in testing

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.

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