rails/actionmailer/test/message_delivery_test.rb
Jeremy Daer e35b98e6f5
Action Mailer: Declarative exception handling with rescue_from.
Follows the same pattern as controllers and jobs. Exceptions raised in
delivery jobs (enqueued by `#deliver_later`) are also delegated to the
mailer's rescue_from handlers, so you can handle the DeserializationError
raised by delivery jobs:

```ruby
class MyMailer < ApplicationMailer
  rescue_from ActiveJob::DeserializationError do
    …
  end
```

ActiveSupport::Rescuable polish:
* Add the `rescue_with_handler` class method so exceptions may be
  handled at the class level without requiring an instance.
* Rationalize `exception.cause` handling. If no handler matches the
  exception, fall back to the handler that matches its cause.
* Handle exceptions raised elsewhere. Pass `object: …` to execute
  the `rescue_from` handler (e.g. a method call or a block to
  instance_exec) against a different object. Defaults to `self`.
2016-05-15 18:44:16 -07:00

154 lines
5.1 KiB
Ruby

require 'abstract_unit'
require 'active_job'
require 'mailers/delayed_mailer'
class MessageDeliveryTest < ActiveSupport::TestCase
include ActiveJob::TestHelper
setup do
@previous_logger = ActiveJob::Base.logger
@previous_delivery_method = ActionMailer::Base.delivery_method
@previous_deliver_later_queue_name = ActionMailer::Base.deliver_later_queue_name
ActionMailer::Base.deliver_later_queue_name = :test_queue
ActionMailer::Base.delivery_method = :test
ActiveJob::Base.logger = Logger.new(nil)
ActionMailer::Base.deliveries.clear
ActiveJob::Base.queue_adapter.perform_enqueued_at_jobs = true
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
DelayedMailer.last_error = nil
DelayedMailer.last_rescue_from_instance = nil
@mail = DelayedMailer.test_message(1, 2, 3)
end
teardown do
ActiveJob::Base.logger = @previous_logger
ActionMailer::Base.delivery_method = @previous_delivery_method
ActionMailer::Base.deliver_later_queue_name = @previous_deliver_later_queue_name
DelayedMailer.last_error = nil
DelayedMailer.last_rescue_from_instance = nil
end
test 'should have a message' do
assert @mail.message
end
test 'its message should be a Mail::Message' do
assert_equal Mail::Message , @mail.message.class
end
test 'should respond to .deliver_later' do
assert_respond_to @mail, :deliver_later
end
test 'should respond to .deliver_later!' do
assert_respond_to @mail, :deliver_later!
end
test 'should respond to .deliver_now' do
assert_respond_to @mail, :deliver_now
end
test 'should respond to .deliver_now!' do
assert_respond_to @mail, :deliver_now!
end
def test_should_enqueue_and_run_correctly_in_activejob
@mail.deliver_later!
assert_equal 1, ActionMailer::Base.deliveries.size
ensure
ActionMailer::Base.deliveries.clear
end
test 'should enqueue the email with :deliver_now delivery method' do
assert_performed_with(job: ActionMailer::DeliveryJob, args: ['DelayedMailer', 'test_message', 'deliver_now', 1, 2, 3]) do
@mail.deliver_later
end
end
test 'should enqueue the email with :deliver_now! delivery method' do
assert_performed_with(job: ActionMailer::DeliveryJob, args: ['DelayedMailer', 'test_message', 'deliver_now!', 1, 2, 3]) do
@mail.deliver_later!
end
end
test 'should enqueue a delivery with a delay' do
travel_to Time.new(2004, 11, 24, 01, 04, 44) do
assert_performed_with(job: ActionMailer::DeliveryJob, at: Time.current.to_f+600.seconds, args: ['DelayedMailer', 'test_message', 'deliver_now', 1, 2, 3]) do
@mail.deliver_later wait: 600.seconds
end
end
end
test 'should enqueue a delivery at a specific time' do
later_time = Time.now.to_f + 3600
assert_performed_with(job: ActionMailer::DeliveryJob, at: later_time, args: ['DelayedMailer', 'test_message', 'deliver_now', 1, 2, 3]) do
@mail.deliver_later wait_until: later_time
end
end
test 'should enqueue the job on the correct queue' do
assert_performed_with(job: ActionMailer::DeliveryJob, args: ['DelayedMailer', 'test_message', 'deliver_now', 1, 2, 3], queue: "test_queue") do
@mail.deliver_later
end
end
test 'can override the queue when enqueuing mail' do
assert_performed_with(job: ActionMailer::DeliveryJob, args: ['DelayedMailer', 'test_message', 'deliver_now', 1, 2, 3], queue: "another_queue") do
@mail.deliver_later(queue: :another_queue)
end
end
test 'deliver_later after accessing the message is disallowed' do
@mail.message # Load the message, which calls the mailer method.
assert_raise RuntimeError do
@mail.deliver_later
end
end
test 'job delegates error handling to mailer' do
# Superclass not rescued by mailer's rescue_from RuntimeError
message = DelayedMailer.test_raise('StandardError')
assert_raise(StandardError) { message.deliver_later }
assert_nil DelayedMailer.last_error
assert_nil DelayedMailer.last_rescue_from_instance
# Rescued by mailer's rescue_from RuntimeError
message = DelayedMailer.test_raise('DelayedMailerError')
assert_nothing_raised { message.deliver_later }
assert_equal 'boom', DelayedMailer.last_error.message
assert_kind_of DelayedMailer, DelayedMailer.last_rescue_from_instance
end
class DeserializationErrorFixture
include GlobalID::Identification
def self.find(id)
raise 'boom, missing find'
end
attr_reader :id
def initialize(id = 1)
@id = id
end
def to_global_id(options = {})
super app: 'foo'
end
end
test 'job delegates deserialization errors to mailer class' do
# Inject an argument that can't be deserialized.
message = DelayedMailer.test_message(DeserializationErrorFixture.new)
# DeserializationError is raised, rescued, and delegated to the handler
# on the mailer class.
assert_nothing_raised { message.deliver_later }
assert_equal DelayedMailer, DelayedMailer.last_rescue_from_instance
assert_equal 'Error while trying to deserialize arguments: boom, missing find', DelayedMailer.last_error.message
end
end