ruby on rails - test - Cómo probar ActionMailer deliver_later con rspec
sendgrid with actionmailer (11)
tratando de actualizar a Rails 4.2, utilizando delayed_job_active_record. No he configurado el backend delayed_job para el entorno de prueba, ya que pensé que los trabajos se ejecutarían de inmediato.
Estoy tratando de probar el nuevo método ''deliver_later'' con Rspec, pero no estoy seguro de cómo.
Código de controlador antiguo:
ServiceMailer.delay.new_user(@user)
Nuevo código de controlador:
ServiceMailer.new_user(@user).deliver_later
Solía probarlo así:
expect(ServiceMailer).to receive(:new_user).with(@user).and_return(double("mailer", :deliver => true))
Ahora recibo errores al usar eso. (El "anuncio publicitario doble" recibió un mensaje inesperado: deliver_later con (sin argumentos))
Sólo
expect(ServiceMailer).to receive(:new_user)
falla también con el ''método indefinido'' deliver_later ''para nil: NilClass''
He probado algunos ejemplos que le permiten ver si los trabajos se ponen en cola usando test_helper en ActiveJob, pero no he podido probar que el trabajo correcto esté en cola.
expect(enqueued_jobs.size).to eq(1)
Esto pasa si se incluye test_helper, pero no me permite verificar si es el correo electrónico correcto que se envía.
Lo que quiero hacer es:
- prueba de que el correo electrónico correcto está en cola (o se ejecuta de inmediato en el entorno de prueba)
- con los parámetros correctos (@user)
¿¿Algunas ideas?? Gracias
Agrega esto:
# spec/support/message_delivery.rb
class ActionMailer::MessageDelivery
def deliver_later
deliver_now
end
end
Referencia: http://mrlab.sk/testing-email-delivery-with-deliver-later.html
Agregaré mi respuesta porque ninguno de los otros fue lo suficientemente bueno para mí:
1) No hay necesidad de burlarse del Mailer: Rails básicamente ya lo hace por usted.
2) No hay necesidad de activar realmente la creación del correo electrónico: ¡esto consumirá tiempo y ralentizará su prueba!
Es por eso que en
environments/test.rb
debería tener configuradas las siguientes opciones:
config.action_mailer.delivery_method = :test
config.active_job.queue_adapter = :test
Una vez más: no entregue sus correos electrónicos con
deliver_now
pero
siempre
use
deliver_later
.
Eso evita que sus usuarios esperen la entrega efectiva del correo electrónico.
Si no tiene
sidekiq
,
sucker_punch
o cualquier otro en producción, simplemente use
config.active_job.queue_adapter = :async
.
Y sea
async
o en
inline
para el entorno de desarrollo.
Dada la siguiente configuración para el entorno de prueba, sus correos electrónicos siempre estarán en cola y nunca se ejecutarán para su entrega: esto evita que se burle de ellos y puede verificar que estén en cola correctamente.
En sus pruebas, siempre divida la prueba en dos: 1) Una prueba unitaria para verificar que el correo electrónico esté en cola correctamente y con los parámetros correctos 2) Una prueba unitaria para el correo para verificar que el asunto, el remitente, el receptor y el contenido son correctos .
Dado el siguiente escenario:
class User
after_update :send_email
def send_email
ReportMailer.update_mail(id).deliver_later
end
end
Escriba una prueba para verificar que el correo electrónico esté en cola correctamente:
include ActiveJob::TestHelper
expect { user.update(name: ''Hello'') }.to have_enqueued_job(ActionMailer::DeliveryJob).with(''ReportMailer'', ''update_mail'', ''deliver_now'', user.id)
y escribe una prueba separada para tu correo electrónico
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
- Ha probado exactamente que su correo electrónico ha sido puesto en cola y no es un trabajo genérico.
- Tu prueba es rapida
- No necesitabas burlarte
Cuando escriba una prueba del sistema, no dude en decidir si realmente desea enviar correos electrónicos allí, ya que la velocidad ya no importa tanto. Personalmente me gusta configurar lo siguiente:
RSpec.configure do |config|
config.around(:each, :mailer) do |example|
perform_enqueued_jobs do
example.run
end
end
end
y asigne el atributo
:mailer
a las pruebas donde realmente quiero enviar correos electrónicos.
Para obtener más información sobre cómo configurar correctamente su correo electrónico en Rails, lea este artículo: https://medium.com/@coorasse/the-correct-emails-configuration-in-rails-c1d8418c0bfd
Esta respuesta es un poco diferente, pero puede ayudar en casos como un nuevo cambio en la API de rails, o un cambio en la forma en que desea entregar (como use
deliver_now
lugar de
deliver_later
).
Lo que hago la mayoría de las veces es pasar un correo como una dependencia del método que estoy probando, pero no paso un correo de rieles, en su lugar paso un objeto que hará las cosas de la "manera que Yo quiero"...
Por ejemplo, si quiero verificar que estoy enviando el correo correcto después del registro de un usuario ... podría hacer ...
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
Y luego, en el controlador donde llamaré a ese método, escribiría la implementación "real" de ese correo ...
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
De esta manera, siento que estoy probando que estoy enviando el mensaje correcto, al objeto correcto, con los datos correctos (argumentos). Y solo necesito crear un objeto muy simple que no tenga lógica, solo la responsabilidad de saber cómo quiere que se llame a ActionMailer.
Prefiero hacer esto porque prefiero tener más control sobre las dependencias que tengo. Este es un ejemplo del "principio de inversión de dependencia" .
No estoy seguro de si es de su agrado, pero es otra forma de resolver el problema =).
He venido aquí buscando una respuesta para una prueba completa, por lo tanto, no solo preguntando si hay un correo esperando a ser enviado, además, para su destinatario, asunto ... etc.
Tengo una solución, que viene de here , pero con un pequeño cambio:
Como dice, la parte curial es
mail = perform_enqueued_jobs { ActionMailer::DeliveryJob.perform_now(*enqueued_jobs.first[:args]) }
El problema es que los parámetros que recibe el correo, en este caso, son diferentes de los parámetros que recibe en producción, en producción, si el primer parámetro es un Modelo, ahora en las pruebas recibirá un hash, por lo que se bloqueará
enqueued_jobs.first[:args]
["UserMailer", "welcome_email", "deliver_now", {"_aj_globalid"=>"gid://forjartistica/User/1"}]
Entonces, si llamamos al remitente como
UserMailer.welcome_email(@user).deliver_later
el remitente recibe en producción un Usuario, pero en la prueba recibirá
{"_aj_globalid"=>"gid://forjartistica/User/1"}
Todos los comentarios serán apreciados. La solución menos dolorosa que he encontrado es cambiar la forma en que llamo a los correos, pasar, la identificación del modelo y no el modelo:
UserMailer.welcome_email(@user.id).deliver_later
Llegué con la misma duda y resolví de una manera menos detallada (una sola línea) inspirada por esta respuesta
expect(ServiceMailer).to receive_message_chain(:new_user, :deliver_later).with(@user).with(no_args)
Tenga en cuenta que el último
with(no_args)
es esencial.
Pero, si no se molesta si se está llamando a
deliver_later
, simplemente haga lo siguiente:
expect(ServiceMailer).to expect(:new_user).with(@user).and_call_original
Si encuentra esta pregunta pero está utilizando ActiveJob en lugar de simplemente DelayedJob por sí solo, y está utilizando Rails 5, le recomiendo configurar ActionMailer en
config/environments/test.rb
:
config.active_job.queue_adapter = :inline
(este era el comportamiento predeterminado antes de Rails 5)
Si te entiendo correctamente, podrías hacer:
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)
La clave es que necesita de alguna manera proporcionar un doble para
deliver_later
.
Una forma simple es:
expect(ServiceMailer).to(
receive(:new_user).with(@user).and_call_original
)
# subject
Una solución mejor (que monkeypatching
deliver_later
) es:
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
El
around { |example| perform_enqueued_jobs(&example) }
around { |example| perform_enqueued_jobs(&example) }
asegura que las tareas en segundo plano se ejecuten antes de verificar los valores de prueba.
Usando ActiveJob y rspec 3.4+ podría usar
have_enqueued_job
esta manera:
expect {
YourMailer.your_method.deliver_later
}.to have_enqueued_job.on_queue(''mailers'')
Esta respuesta es para Rails Test, no para rspec ...
Si está utilizando
delivery_later
así:
# 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
Puede verificar en su prueba si el correo electrónico se ha agregado a la cola:
# 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
Sin embargo, si hace esto, se sorprenderá de la prueba fallida que le indica que afirmar_enqueued_jobs no está definido para que lo usemos.
Esto se debe a que nuestra prueba hereda de ActionController :: TestCase que, al momento de escribir, no incluye ActiveJob :: TestHelper.
Pero podemos solucionar esto rápidamente:
# test/test_helper.rb class ActionController::TestCase include ActiveJob::TestHelper … end
Referencia: https://www.engineyard.com/blog/testing-async-emails-rails-42