Подтвердить что ты не робот

Как протестировать ActionMailer deliver_later с помощью rspec

пытается выполнить обновление до Rails 4.2, используя delayed_job_active_record. Я не устанавливал backend_job backend для тестовой среды, так как думал, что задания будут выполняться сразу же.

Я пытаюсь протестировать новый метод "deliver_later" с помощью Rspec, но я не уверен, как это сделать.

Старый код контроллера:

ServiceMailer.delay.new_user(@user)

Новый код контроллера:

ServiceMailer.new_user(@user).deliver_later

ИСПОЛЬЗУЕМЫЙ, чтобы проверить его так:

expect(ServiceMailer).to receive(:new_user).with(@user).and_return(double("mailer", :deliver => true))

Теперь я получаю ошибки, используя это. (Двойная "почтовая программа" получила неожиданное сообщение: deliver_later с (без аргументов))

Просто

expect(ServiceMailer).to receive(:new_user)

тоже не работает с методом < undefined `deliver_later 'для nil: NilClass

Я пробовал некоторые примеры, которые позволяют вам видеть, заданы ли задания с помощью test_helper в ActiveJob, но мне не удалось проверить, что правильная работа поставлена ​​в очередь.

expect(enqueued_jobs.size).to eq(1)

Это передается, если включен test_helper, но он не позволяет мне проверять, отправляется ли оно правильно.

Что я хочу сделать:

  • проверьте, что правильный адрес электронной почты поставлен в очередь (или выполняется сразу в тестовом env).
  • с правильными параметрами (@user)

Любые идеи? спасибо

4b9b3361

Ответ 1

Если вы правильно поняли, вы могли бы сделать:

message_delivery = instance_double(ActionMailer::MessageDelivery)
expect(ServiceMailer).to receive(:new_user).with(@user).and_return(message_delivery)
allow(message_delivery).to receive(:deliver_later)

Главное, что вам нужно как-то предоставить double для deliver_later.

Ответ 2

Если вы нашли этот вопрос, но используете ActiveJob, а не просто DelayedJob самостоятельно, и используете Rails 5, я рекомендую настроить ActionMailer в config/environments/test.rb:

config.active_job.queue_adapter = :inline

(это было поведение по умолчанию до Rails 5)

Ответ 3

Используя ActiveJob и rspec 3. 4+ вы можете использовать have_enqueued_job следующим образом:

expect {
  YourMailer.your_method.deliver_later
}.to have_enqueued_job.on_queue('mailers')

Ответ 4

Я добавлю свой ответ, потому что ни один из остальных не был достаточно хорош для меня:

1) Нет необходимости издеваться над Mailer: Rails в основном делает это уже для вас.

2) Нет необходимости действительно инициировать создание электронного письма: это потребует времени и замедлит ваш тест!

Поэтому в environments/test.rb должны быть установлены следующие параметры:

config.action_mailer.delivery_method = :test
config.active_job.queue_adapter = :test

Опять же: не доставляйте свои электронные письма, используя deliver_now, но всегда используйте deliver_later. Это мешает вашим пользователям ждать эффективной доставки электронной почты. Если у вас нет sidekiq, sucker_punch или чего-либо другого в производстве, просто используйте config.active_job.queue_adapter = :async. И async, или inline для среды разработки.

Принимая во внимание следующую конфигурацию для среды тестирования, ваши электронные письма всегда будут помещены в очередь и никогда не будут выполнены для доставки: это предотвращает их насмешку, и вы можете проверить, правильно ли они поставлены в очередь.

В ваших тестах всегда разделяет тест на две части: 1) Один unit тест, чтобы проверить, что электронная почта поставлена в очередь правильно и с правильными параметрами 2) Один unit тест для почты, чтобы проверить правильность темы, отправителя, получателя и содержимого.

Учитывая следующий сценарий:

class User
  after_update :send_email

  def send_email
    ReportMailer.update_mail(id).deliver_later
  end
end

Напишите тест для проверки правильности постановки электронного письма в очередь:

include ActiveJob::TestHelper
expect { user.update(name: 'Hello') }.to have_enqueued_job(ActionMailer::DeliveryJob).with('ReportMailer', 'update_mail', 'deliver_now', user.id)

и напишите отдельный тест для своей электронной почты

Rspec.describe ReportMailer do
    describe '#update_email' do
      subject(:mailer) { described_class.update_email(user.id) }
      it { expect(mailer.subject).to eq 'whatever' }
      ...
    end
end
  • Вы точно проверили, что ваша электронная почта поставлена в очередь, а не является обычной работой.
  • Ваш тест быстрый
  • Тебе не нужно было насмехаться

Когда вы пишете системный тест, не стесняйтесь решать, хотите ли вы действительно доставлять туда электронные письма, так как скорость уже не так важна. Мне лично нравится настраивать следующее:

RSpec.configure do |config|
  config.around(:each, :mailer) do |example|
    perform_enqueued_jobs do
      example.run
    end
  end
end

и назначьте атрибут :mailer для тестов, где я хочу отправлять электронные письма.

Подробнее о том, как правильно настроить электронную почту в Rails, читайте в этой статье: https://medium.com/@coorasse/the-correct-emails-configuration-in-rails-c1d8418c0bfd

Ответ 6

Более приятное решение (чем monkeypatching deliver_later):

require 'spec_helper'
include ActiveJob::TestHelper

describe YourObject do
  around { |example| perform_enqueued_jobs(&example) }

  it "sends an email" do
    expect { something_that.sends_an_email }.to change(ActionMailer::Base.deliveries, :length)
  end
end

around { |example| perform_enqueued_jobs(&example) } обеспечивает выполнение фоновых задач перед проверкой тестовых значений.

Ответ 7

Я пришел с такими же сомнениями и решил в менее подробном (однострочном) способе, вдохновленном этим ответом

expect(ServiceMailer).to receive_message_chain(:new_user, :deliver_later).with(@user).with(no_args)

Обратите внимание, что последний with(no_args) является существенным.

Но, если вы не беспокоитесь, если вызов deliver_later вызывается, просто выполните:

expect(ServiceMailer).to expect(:new_user).with(@user).and_call_original

Ответ 8

Простой способ:

expect(ServiceMailer).to(
  receive(:new_user).with(@user).and_call_original
)
# subject

Ответ 9

Я обнаружил, что мой RSpec foo не справился с задачей измерения "deliver_later".

В течение 20 секунд "get" er done "я пошел с:

MyMailer.send(ENV['RAILS_ENV'] == 'test' ? :deliver : :deliver_later)

Ответ 10

Я пришел сюда, чтобы найти ответ для полного тестирования, поэтому не просто спрашивает, есть ли почта, ожидающая отправки, кроме того, для ее получатель, субъект... и т.д.

У меня есть решение, чем из здесь, но с небольшим изменением:

Как говорится, кураторская часть

mail = perform_enqueued_jobs { ActionMailer::DeliveryJob.perform_now(*enqueued_jobs.first[:args]) }

Проблема заключается в том, что параметры, чем получатель, в этом случае отличаются от параметров, чем получают в процессе производства, при производстве, если первый параметр является моделью, теперь при тестировании будет получен хэш, поэтому произойдет сбой

enqueued_jobs.first[:args]
["UserMailer", "welcome_email", "deliver_now", {"_aj_globalid"=>"gid://forjartistica/User/1"}]

Итак, если мы будем называть почтовую программу как UserMailer.welcome_email(@user).deliver_later, почтовая программа получает в производство пользователя, но при тестировании получит {"_aj_globalid"=>"gid://forjartistica/User/1"}

Все комментарии будут оценены, Менее болезненное решение, которое я нашел, изменяет способ, которым я называю почтовые программы, прохождение, идентификатор модели, а не модель:

UserMailer.welcome_email(@user.id).deliver_later

Ответ 11

Этот ответ немного отличается, но может помочь в таких случаях, как новое изменение в API рельсов или изменение способа доставки (например, используйте deliver_now вместо deliver_later).

В большинстве случаев я передаю почтовую программу в качестве зависимости от метода, который я тестирую, но я не передаю почтовую программу из рельсов, вместо этого я передаю объект, который будет выполнять действия "таким образом, чтобы Я хочу "...

Например, если я хочу проверить, что я отправляю правильное письмо после регистрации пользователя... я мог бы сделать...

class DummyMailer
  def self.send_welcome_message(user)
  end
end

it "sends a welcome email" do
  allow(store).to receive(:create).and_return(user)
  expect(mailer).to receive(:send_welcome_message).with(user)
  register_user(params, store, mailer)
end

А затем в контроллере, где я буду вызывать этот метод, я напишу "реальную" реализацию этого почтовика...

class RegistrationsController < ApplicationController
  def create
    Registrations.register_user(params[:user], User, Mailer)
    # ...
  end

  class Mailer
    def self.send_welcome_message(user)
      ServiceMailer.new_user(user).deliver_later
    end
  end
end

Таким образом, я чувствую, что проверяю, что я отправляю правильное сообщение нужному объекту с правильными данными (аргументами). И мне просто нужно создать очень простой объект, который не имеет логики, а просто обязан знать, как ActionMailer хочет вызываться.

Я предпочитаю делать это, потому что я предпочитаю иметь больше контроля над моими зависимостями. Это пример для меня "принципа обращения зависимостей".

Я не уверен, если это ваш вкус, но это еще один способ решения проблемы =).

Ответ 12

Этот ответ предназначен для Rails Test, а не для rspec...

Если вы используете delivery_later следующим образом:

# app/controllers/users_controller.rb 

class UsersController < ApplicationController 
  … 
  def create 
    … 
    # Yes, Ruby 2.0+ keyword arguments are preferred 
    UserMailer.welcome_email(user: @user).deliver_later 
  end 
end 

Вы можете проверить в своем тесте, было ли письмо добавлено в очередь:

# test/controllers/users_controller_test.rb 

require 'test_helper' 

class UsersControllerTest < ActionController::TestCase 
  … 
  test 'email is enqueued to be delivered later' do 
    assert_enqueued_jobs 1 do 
      post :create, {…} 
    end 
  end 
end 

Если вы сделаете это, вы будете удивлены неудачным тестом, который сообщает, что assert_enqueued_jobs не определен для использования нами.

Это связано с тем, что наш тест наследуется от ActionController :: TestCase, который на момент написания не включал ActiveJob :: TestHelper.

Но мы можем быстро это исправить:

# test/test_helper.rb 

class ActionController::TestCase 
  include ActiveJob::TestHelper 
  … 
end 

Ссылка:https://www.engineyard.com/blog/testing-async-emails-rails-42