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が含まれている場合、これは成功しますが、送信されている正しいメールであることを確認することはできません。
私がやりたいことは:
何か案は??ありがとう
私があなたを正しく理解していれば、あなたは次のことができます:
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を提供する必要があることです。
この質問を見つけたが、単にDelayedJobを単独で使用するのではなくActiveJobを使用し、Rails 5を使用している場合、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)メーラーをモックする必要はありません:Railsは基本的に既にあなたのためにそれを行います。
2)メールの作成を実際にトリガーする必要はありません。これにより、時間がかかり、テストが遅くなります。
そのため、environments/test.rb
で次のオプションを設定する必要があります。
config.action_mailer.delivery_method = :test
config.active_job.queue_adapter = :test
繰り返しますが、deliver_now
を使用してメールを配信しないでください。ただし、alwaysdeliver_later
を使用してください。これにより、ユーザーは電子メールの効果的な配信を待つことができなくなります。 sidekiq
、sucker_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
これを追加:
# 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
この答えは少し異なりますが、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" の例です。
それがあなたの好みかどうかはわかりませんが、問題を解決する別の方法です=)。
完全なテストの答えを探してここに来たので、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