Versuch, mit delay_job_active_record ein Upgrade auf Rails 4.2 durchzuführen. Ich habe das verzögerte_job-Backend für die Testumgebung nicht so festgelegt, dass Jobs sofort ausgeführt werden.
Ich versuche, die neue Methode 'Deliver_Later' mit Rspec zu testen, bin mir aber nicht sicher, wie.
Alter Controller-Code:
ServiceMailer.delay.new_user(@user)
Neuer Controller-Code:
ServiceMailer.new_user(@user).deliver_later
Ich habe es so getestet:
expect(ServiceMailer).to receive(:new_user).with(@user).and_return(double("mailer", :deliver => true))
Jetzt bekomme ich Fehler damit. (Doppelter "Mailer" hat unerwartete Nachricht erhalten: Deliver_Later mit (keine Argumente))
Gerade
expect(ServiceMailer).to receive(:new_user)
schlägt auch mit 'undefinierter Methode' liefern_later 'für nil: NilClass' fehl
Ich habe einige Beispiele ausprobiert, mit denen Sie mithilfe von test_helper in ActiveJob feststellen können, ob Jobs in die Warteschlange gestellt wurden. Es ist mir jedoch nicht gelungen, zu testen, ob der richtige Job in der Warteschlange steht.
expect(enqueued_jobs.size).to eq(1)
Dies ist erfolgreich, wenn test_helper enthalten ist, aber ich kann nicht überprüfen, ob es sich um die richtige E-Mail handelt, die gesendet wird.
Was ich tun möchte ist:
- Testen Sie, ob die richtige E-Mail in der Warteschlange steht (oder sofort in test env ausgeführt wird).
- mit den richtigen Parametern (@user)
Irgendwelche Ideen?? Vielen Dank
quelle
Antworten:
Wenn ich Sie richtig verstehe, können Sie Folgendes tun:
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)
Der Schlüssel ist, dass Sie irgendwie ein Doppel für bereitstellen müssen
deliver_later
.quelle
allow(message_delivery).to …
? Schließlich testen Sie das Ergebnis bereits durch Erwartungnew_user
.deliver_later
Methode aus dem Controller entfernt wird, indemallow
wir sie nicht verwenden können das richtig verstehen? Ich meine, der Test wird noch bestehen. Denken Sie immer noch, dass die Verwendung einesallow
Willens eine bessere Idee ist als die Verwendungexpect
? Ich habe dieseexpect
Flaggen gesehen, wenn sie versehentlichdeliver_later
entfernt wurden und deshalb wollte ich das im Allgemeinen diskutieren. Es wäre großartig, wenn Sie näher erläutern könnten, warumallow
der obige Kontext besser ist.ServiceMailer
'new_user
aufgerufen wird. Sie können einen weiteren Test erstellen, derdeliver_later
die aufgerufene Methode testet , sobald die E-Mail erstellt wurde.allow
basierend auf dem Kontext der Testmethode vorgeschlagen habenServiceMailer's new_user
. Für den Fall, dass ich testen müsstedeliver_later
, dachte ich, ich würde dem vorhandenen Test (der nachServiceMailer's new_user
Methoden sucht) einfach eine weitere Aussage hinzufügen , um so etwas zu überprüfen,expect(mailer_object).to receive(:deliver_later)
anstatt dies als einen weiteren Test insgesamt zu testen. Es wäre interessant zu wissen, warum Sie einen separaten Test dafür bevorzugen, falls wir jemals testen müsstendeliver_later
.Mit ActiveJob und
rspec-rails
3.4+ können Sie Folgendes verwendenhave_enqueued_job
:expect { YourMailer.your_method.deliver_later # or any other method that eventually would trigger mail enqueuing }.to( have_enqueued_job.on_queue('mailers').with( # `with` isn't mandatory, but it will help if you want to make sure is # the correct enqueued mail. 'YourMailer', 'your_method', 'deliver_now', any_param_you_want_to_check ) )
Sie können auch doppelt einchecken
config/environments/test.rb
:config.action_mailer.delivery_method = :test config.active_job.queue_adapter = :test
Eine andere Möglichkeit wäre, Inline-Jobs auszuführen:
config.active_job.queue_adapter = :inline
Beachten Sie jedoch, dass dies die Gesamtleistung Ihrer Testsuite beeinträchtigen würde , da alle Ihre Jobs ausgeführt werden, sobald sie in die Warteschlange gestellt werden.
quelle
Wenn Sie diese Frage finden, aber ActiveJob anstelle von DelayedJob verwenden und Rails 5 verwenden, empfehle ich, ActionMailer wie folgt zu konfigurieren
config/environments/test.rb
:config.active_job.queue_adapter = :inline
(Dies war das Standardverhalten vor Rails 5)
quelle
Ich werde meine Antwort hinzufügen, weil keiner der anderen gut genug für mich war:
1) Es ist nicht nötig, den Mailer zu verspotten: Rails erledigt das im Grunde schon für Sie.
2) Es ist nicht notwendig, die Erstellung der E-Mail wirklich auszulösen: Dies kostet Zeit und verlangsamt Ihren Test!
Aus diesem Grund sollten in
environments/test.rb
Ihnen die folgenden Optionen festgelegt sein:config.action_mailer.delivery_method = :test config.active_job.queue_adapter = :test
Nochmals: nicht liefern Ihre E - Mails mit
deliver_now
aber immer verwendendeliver_later
. Dies verhindert, dass Ihre Benutzer auf die effektive Zustellung der E-Mail warten. Wenn Sie nicht habensidekiq
,sucker_punch
oder jede andere in der Produktion, einfach zu verwendenconfig.active_job.queue_adapter = :async
. Und entwederasync
oderinline
für die Entwicklungsumgebung.Bei der folgenden Konfiguration für die Testumgebung werden Ihre E-Mails immer in die Warteschlange gestellt und niemals zur Zustellung ausgeführt: Dies verhindert, dass Sie sie verspotten, und Sie können überprüfen, ob sie korrekt in die Warteschlange gestellt wurden.
Teilen Sie den Test in Ihren Tests immer in zwei Teile auf: 1) Ein Einheitentest, um zu überprüfen, ob die E-Mail korrekt in die Warteschlange gestellt wurde und mit den richtigen Parametern. 2) Ein Einheitentest für die E-Mail, um zu überprüfen, ob Betreff, Absender, Empfänger und Inhalt korrekt sind .
Angesichts des folgenden Szenarios:
class User after_update :send_email def send_email ReportMailer.update_mail(id).deliver_later end end
Schreiben Sie einen Test, um zu überprüfen, ob die E-Mail korrekt in die Warteschlange gestellt wurde:
include ActiveJob::TestHelper expect { user.update(name: 'Hello') }.to have_enqueued_job(ActionMailer::DeliveryJob).with('ReportMailer', 'update_mail', 'deliver_now', user.id)
und schreiben Sie einen separaten Test für Ihre E-Mail
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
Wenn Sie einen Systemtest schreiben, können Sie frei entscheiden, ob Sie dort wirklich E-Mails versenden möchten, da die Geschwindigkeit nicht mehr so wichtig ist. Ich persönlich möchte Folgendes konfigurieren:
RSpec.configure do |config| config.around(:each, :mailer) do |example| perform_enqueued_jobs do example.run end end end
und weisen Sie das
:mailer
Attribut den Tests zu, bei denen ich tatsächlich E-Mails senden möchte.Weitere Informationen zum korrekten Konfigurieren Ihrer E-Mail in Rails finden Sie in diesem Artikel: https://medium.com/@coorasse/the-correct-emails-configuration-in-rails-c1d8418c0bfd
quelle
ActionMailer::MailDeliveryJob
ActionMailer::DeliveryJob
Füge das hinzu:
# spec/support/message_delivery.rb class ActionMailer::MessageDelivery def deliver_later deliver_now end end
Referenz: http://mrlab.sk/testing-email-delivery-with-deliver-later.html
quelle
deliver_later(wait: 2.minutes)
. Also habe ichdeliver_later(options={})
NameError: uninitialized constant ActionMailer
Eine schönere Lösung (als Monkeypatching
deliver_later
) ist: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
Die
around { |example| perform_enqueued_jobs(&example) }
sorgt dafür , dass Hintergrundaufgaben werden ausgeführt werden, bevor die Testwerte zu überprüfen.quelle
Ich kam mit dem gleichen Zweifel und beschloss auf eine weniger ausführliche (einzeilige) Weise, inspiriert von dieser Antwort
expect(ServiceMailer).to receive_message_chain(:new_user, :deliver_later).with(@user).with(no_args)
Beachten Sie, dass der letzte
with(no_args)
wesentlich ist.Aber wenn Sie sich nicht darum kümmern, ob
deliver_later
angerufen wird, tun Sie einfach:expect(ServiceMailer).to expect(:new_user).with(@user).and_call_original
quelle
Ein einfacher Weg ist:
expect(ServiceMailer).to( receive(:new_user).with(@user).and_call_original ) # subject
quelle
Für aktuelle Googler:
allow(YourMailer).to receive(:mailer_method).and_call_original expect(YourMailer).to have_received(:mailer_method)
quelle
Diese Antwort ist für Rails Test, nicht für rspec ...
Wenn Sie
delivery_later
wie folgt verwenden:# 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
Sie können in Ihrem Test überprüfen, ob die E-Mail zur Warteschlange hinzugefügt wurde:
# 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
Wenn Sie dies jedoch tun, werden Sie von dem fehlgeschlagenen Test überrascht sein, der besagt, dass assert_enqueued_jobs für uns nicht definiert ist.
Dies liegt daran, dass unser Test von ActionController :: TestCase erbt, das zum Zeitpunkt des Schreibens ActiveJob :: TestHelper nicht enthält.
Aber wir können das schnell beheben:
# test/test_helper.rb class ActionController::TestCase include ActiveJob::TestHelper … end
Referenz: https://www.engineyard.com/blog/testing-async-emails-rails-42
quelle
Ich bin hierher gekommen, um eine Antwort für einen vollständigen Test zu suchen. Ich habe also nicht nur gefragt, ob eine E-Mail darauf wartet, gesendet zu werden, sondern auch den Empfänger, den Betreff usw.
Ich habe eine Lösung, als von hier kommt , aber mit einer kleinen Änderung:
Wie es heißt, ist der kuriale Teil
mail = perform_enqueued_jobs { ActionMailer::DeliveryJob.perform_now(*enqueued_jobs.first[:args]) }
Das Problem ist, dass sich die Parameter, die der Mailer empfängt, in diesem Fall von den Parametern unterscheiden, die in der Produktion empfangen werden. Wenn der erste Parameter ein Modell ist, erhält der jetzt im Test befindliche Parameter einen Hash und stürzt ab
enqueued_jobs.first[:args] ["UserMailer", "welcome_email", "deliver_now", {"_aj_globalid"=>"gid://forjartistica/User/1"}]
Wenn wir also den Mailer so anrufen, wie
UserMailer.welcome_email(@user).deliver_later
der Mailer in der Produktion einen Benutzer empfängt, wird er beim Testen empfangen{"_aj_globalid"=>"gid://forjartistica/User/1"}
Alle Kommentare werden geschätzt. Die weniger schmerzhafte Lösung, die ich gefunden habe, besteht darin, die Art und Weise zu ändern, wie ich die Mailer anrufe, die ID des Modells und nicht das Modell weiterzuleiten:
UserMailer.welcome_email(@user.id).deliver_later
quelle
Diese Antwort ist etwas anders, kann jedoch in Fällen wie einer neuen Änderung der Rails-API oder einer Änderung der Art und Weise, wie Sie liefern möchten, hilfreich sein (z. B. Verwendung
deliver_now
anstelle vondeliver_later
).Was ich die meiste Zeit mache, ist, einen Mailer als Abhängigkeit von der Methode zu übergeben, die ich teste, aber ich übergebe keinen Mailer von Schienen, sondern übergebe stattdessen ein Objekt, das die Dinge auf die "Art und Weise" erledigt Ich will"...
Wenn ich zum Beispiel überprüfen möchte, ob ich nach der Registrierung eines Benutzers die richtige Mail sende ... könnte ich ...
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
Und dann würde ich in dem Controller, in dem ich diese Methode aufrufen werde, die "echte" Implementierung dieses Mailers schreiben ...
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
Auf diese Weise habe ich das Gefühl, dass ich teste, dass ich die richtige Nachricht mit den richtigen Daten (Argumenten) an das richtige Objekt sende. Und ich muss nur ein sehr einfaches Objekt erstellen, das keine Logik hat, sondern nur die Verantwortung, zu wissen, wie ActionMailer aufgerufen werden soll.
Ich mache das lieber, weil ich lieber mehr Kontrolle über meine Abhängigkeiten habe. Dies ist für mich ein Beispiel für das "Prinzip der Abhängigkeitsinversion" .
Ich bin nicht sicher, ob es Ihr Geschmack ist, aber es ist ein anderer Weg, um das Problem zu lösen =).
quelle