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

попытка обновить до направляющих 4.2, использование delayed_job_active_record. Я не установил delayed_job бэкенд для тестовой среды, как думается тот способ, которым задания немедленно выполнились бы.

Я пытаюсь протестировать новый '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 с (никакой args))

Просто

expect(ServiceMailer).to receive(:new_user)

сбои также с 'неопределенным методом 'deliver_later' для nil:NilClass'

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

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

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

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

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

Какие-либо идеи??спасибо

62
задан 4 January 2015 в 10:20

10 ответов

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

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)

ключевая вещь состоит в том, что необходимо так или иначе обеспечить двойное для deliver_later.

71
ответ дан 31 October 2019 в 13:21

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

config.active_job.queue_adapter = :inline

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

34
ответ дан 31 October 2019 в 13:21

Используя ActiveJob и rspec 3.4 + Вы могли использовать have_enqueued_job как это:

expect {
  YourMailer.your_method.deliver_later
}.to have_enqueued_job.on_queue('mailers')
34
ответ дан 31 October 2019 в 13:21

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

1) нет никакой потребности дразнить Почтовую программу: Направляющие в основном уже делают это для Вас.

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) Один модульный тест, чтобы проверить, что электронная почта ставится в очередь правильно и с корректными параметрами 2) Один модульный тест на почту, чтобы проверить, что предмет, отправитель, получатель и содержание корректны.

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

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, атрибут к тестам был, я хочу на самом деле послать электронные письма.

Для больше о том, как правильно настроить Вашу электронную почту в направляющих, читает эту статью: https://medium.com / coorasse/the-correct-emails-configuration-in-rails-c1d8418c0bfd

15
ответ дан 31 October 2019 в 13:21

Добавьте это:

# spec/support/message_delivery.rb
class ActionMailer::MessageDelivery
  def deliver_later
    deliver_now
  end
end

Ссылка: http://mrlab.sk/testing-email-delivery-with-deliver-later.html

8
ответ дан 31 October 2019 в 13:21

Более хорошее решение (чем 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
ответ дан 31 October 2019 в 13:21

Я шел с тем же сомнением и разрешил в менее подробном (одна строка) путь, вдохновленный этот ответ

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

3
ответ дан 31 October 2019 в 13:21

Простой путь:

expect(ServiceMailer).to(
  receive(:new_user).with(@user).and_call_original
)
# subject
2
ответ дан 31 October 2019 в 13:21

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

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

, Как это говорит, curial часть

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

0
ответ дан 31 October 2019 в 13:21

Этот ответ немного отличается, но может помочь в случаях как новое изменение в направляющих 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 хочет быть названным.

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

я не уверен - ли это Ваш вкус, но является другим способом решить проблему =).

0
ответ дан 31 October 2019 в 13:21

Другие вопросы по тегам:

Похожие вопросы: