web-dev-qa-db-ja.com

rspecでActionMailer deliver_laterをテストする方法

Rails 4.2を使用して、delayed_job_active_recordを使用します。ジョブがすぐに実行されるように、テスト環境にdelay_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)

ただ

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)

何か案は??ありがとう

58
bobomoreno

私があなたを正しく理解していれば、あなたは次のことができます:

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に何らかの方法でdoubleを提供する必要があることです。

69
Peter Alfvin

この質問を見つけたが、単にDelayedJobを単独で使用するのではなくActiveJobを使用し、Rails 5を使用している場合、config/environments/test.rb

config.active_job.queue_adapter = :inline

(これはRails 5)より前のデフォルトの動作でした)

33
Gabe Kopley

ActiveJobとrspec 3.4+を使用すると、 have_enqueued_job このように:

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

他のどれも私にとって十分ではなかったので、私は私の答えを追加します:

1)メーラーをモックする必要はありません:Railsは基本的に既にあなたのためにそれを行います。

2)メールの作成を実際にトリガーする必要はありません。これにより、時間がかかり、テストが遅くなります。

そのため、environments/test.rbで次のオプションを設定する必要があります。

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

繰り返しますが、deliver_nowを使用してメールを配信しないでください。ただし、alwaysdeliver_laterを使用してください。これにより、ユーザーは電子メールの効果的な配信を待つことができなくなります。 sidekiqsucker_punch、または本番環境にその他のものがない場合は、config.active_job.queue_adapter = :asyncを使用してください。また、開発環境の場合はasyncまたはinlineのいずれか。

テスト環境の次の構成を考えると、電子メールは常にキューに入れられ、配信のために実行されることはありません。これにより、電子メールがモックされなくなり、正しくキューに入れられていることを確認できます。

テストでは、alwaysテストを2つに分割します。1)1つのユニットテストで、電子メールが正しく、正しいパラメーターでエンキューされていることを確認します2)1つメールの単体テストで、件名、送信者、受信者、内容が正しいことを確認します。

次のシナリオを考えます:

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

12
coorasse

これを追加:

# 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
Minimul

(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
Qqwy

私は同じ疑問を抱き、 この答え に触発されて、より冗長な(単一行の)方法で解決しました。

_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
Luccas

簡単な方法は次のとおりです。

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

この答えは少し異なりますが、Rails APIの新しい変更、または配信方法の変更(代わりにdeliver_nowを使用するなど) deliver_later)。

ほとんどの場合、テストしているメソッドの依存関係としてメーラーを渡しますが、Railsからメーラーを渡すのではなく、代わりに「が欲しいです"...

たとえば、ユーザーの登録後に正しいメールを送信していることを確認したい場合は...

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がどのように呼び出されたいかを知る責任があるだけです。

私が持っている依存関係をより細かく制御したいので、これを行うことを好みます。これは "Dependency inversionprinciple" の例です。

それがあなたの好みかどうかはわかりませんが、問題を解決する別の方法です=)。

0
Benito Serna

完全なテストの答えを探してここに来たので、notjust受信者、件名などに加えて、送信待ちのメールが1つあるかどうかを尋ねる

here から得られるものよりも解決策がありますが、少し変更されています:

それが言うように、最終的な部分は

_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"}_を受け取ります

すべてのコメントに感謝します。私が見つけた苦痛の少ない解決策は、メーラーを呼び出す方法を変更することです。モデルではなく、モデルのIDを渡します:

UserMailer.welcome_email(@user.id).deliver_later

0
Albert Català