Program Tip

rspec으로 ActionMailer delivery_later를 테스트하는 방법

programtip 2020. 11. 29. 12:10
반응형

rspec으로 ActionMailer delivery_later를 테스트하는 방법


delayed_job_active_record를 사용하여 Rails 4.2로 업그레이드하려고합니다. 작업이 바로 실행될 것이라고 생각했기 때문에 테스트 환경에 대한 delayed_job 백엔드를 설정하지 않았습니다.

Rspec을 사용하여 새로운 'deliver_later'메서드를 테스트하려고하는데 방법을 잘 모르겠습니다.

이전 컨트롤러 코드 :

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 with (no args))

다만

expect(ServiceMailer).to receive(:new_user)

nil : NilClass에 대한 'undefined method`deliver_later'도 실패합니다.

ActiveJob에서 test_helper를 사용하여 작업이 대기열에 추가되었는지 확인할 수있는 몇 가지 예제를 시도했지만 올바른 작업이 대기열에 있는지 테스트하지 못했습니다.

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

test_helper가 포함 된 경우 통과하지만 전송중인 이메일이 올바른지 확인할 수 없습니다.

내가하고 싶은 것은 :

  • 올바른 이메일이 대기열에 있는지 테스트 (또는 테스트 환경에서 바로 실행)
  • 올바른 매개 변수 (@user)

어떤 아이디어 ?? 감사


내가 당신을 올바르게 이해한다면 다음과 같이 할 수 있습니다.

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.


이 질문을 찾았지만 단순히 DelayedJob이 아닌 ActiveJob을 사용하고 Rails 5를 사용하고 있다면 ActionMailer를 config/environments/test.rb다음 에서 구성하는 것이 좋습니다 .

config.active_job.queue_adapter = :inline

(이것은 Rails 5 이전의 기본 동작이었습니다)


ActiveJob 및 rspec 3.4+를 사용하면 다음과 have_enqueued_job같이 사용할 수 있습니다 .

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

다른 것 중 어느 것도 나에게 충분하지 않았기 때문에 내 대답을 추가하겠습니다.

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) 이메일이 올바른 매개 변수로 대기열에 올바로 추가되었는지 확인하기위한 하나의 단위 테스트 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실제로 이메일을 보내고 싶었던 테스트에 속성을 할당합니다 .

Rails에서 이메일을 올바르게 구성하는 방법에 대한 자세한 내용은 https://medium.com/@coorasse/the-correct-emails-configuration-in-rails-c1d8418c0bfd 문서를 참조하세요.


이거 추가 해봐:

# 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


더 좋은 해결책 (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) }보장하지만 백그라운드 작업은 테스트 값을 확인하기 전에 실행됩니다.


나는 같은 의심을 가지고 왔고이 답변에서 영감을 얻은 덜 장황한 (한 줄) 방식으로 해결했습니다.

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


간단한 방법은 다음과 같습니다.

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

I have come here looking for an answer for a complete testing, so, not just asking if there is one mail waiting to be sent, in addition, for its recipient, subject...etc

I have a solution, than comes from here, but with a little change:

As it says, the curial part is

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

The problem is that the parameters than mailer receives, in this case, is different from the parameters than receives in production, in production, if the first parameter is a Model, now in testing will receive a hash, so will crash

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

So, if we call the mailer as UserMailer.welcome_email(@user).deliver_later the mailer receives in production a User, but in testing will receive {"_aj_globalid"=>"gid://forjartistica/User/1"}

All comments will be appreciate, The less painful solution I have found is changing the way that I call the mailers, passing, the model's id and not the model:

UserMailer.welcome_email(@user.id).deliver_later


This answer is a little bit different, but may help in cases like a new change in the rails API, or a change in the way you want to deliver (like use deliver_now instead of deliver_later).

What I do most of the time is to pass a mailer as a dependency to the method that I am testing, but I don't pass an mailer from rails, I instead pass an object that will do the the things in the "way that I want"...

For example if I want to check that I am sending the right mail after the registration of a user... I could do...

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

And then in the controller where I will be calling that method, I would write the "real" implementation of that mailer...

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

In this way I feel that I am testing that I am sending the right message, to the right object, with the right data (arguments). And I am just in need of creating a very simple object that has no logic, just the responsibility of knowing how ActionMailer wants to be called.

I prefer to do this because I prefer to have more control over the dependencies I have. This is form me an example of the "Dependency inversion principle".

I am not sure if it is your taste, but is another way to solve the problem =).

참고URL : https://stackoverflow.com/questions/27647749/how-to-test-actionmailer-deliver-later-with-rspec

반응형