with test rails guide for ruby-on-rails rspec actionmailer delayed-job rails-activejob

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



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