ruby on rails - Como testar o ActionMailer deliver_later com rspec




ruby-on-rails delayed-job (8)

tentando atualizar para o Rails 4.2, usando delayed_job_active_record. Eu não defini o back-end delayed_job para o ambiente de teste como pensava que os trabalhos seriam executados imediatamente.

Estou tentando testar o novo método 'deliver_later' com o Rspec, mas não sei como.

Código antigo do controlador:

ServiceMailer.delay.new_user(@user)

Novo código do controlador:

ServiceMailer.new_user(@user).deliver_later

Eu usei para testá-lo assim:

expect(ServiceMailer).to receive(:new_user).with(@user).and_return(double("mailer", :deliver => true))

Agora eu recebo erros usando isso. (O "mailer" duplo recebeu uma mensagem inesperada: deliver_later with (no args))

Somente

expect(ServiceMailer).to receive(:new_user)

falha também com o 'método indefinido `deliver_later' para nil: NilClass '

Eu tentei alguns exemplos que permitem ver se os trabalhos estão na fila usando test_helper no ActiveJob, mas não consegui testar se o trabalho correto está na fila.

expect(enqueued_jobs.size).to eq(1)

Isso passa se o test_helper estiver incluído, mas não me permite verificar se o email correto está sendo enviado.

O que eu quero fazer é:

  • testar se o email correto está na fila (ou executado imediatamente no ambiente de teste)
  • com os parâmetros corretos (@user)

Alguma ideia?? obrigado


Acrescentarei minha resposta porque nenhum dos outros foi bom o suficiente para mim:

1) Não há necessidade de zombar do Mailer: o Rails basicamente já faz isso por você.

2) Não há necessidade de realmente ativar a criação do email: isso consumirá tempo e tornará mais lento o seu teste!

É por isso que em environments/test.rb você deve ter as seguintes opções definidas:

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

Novamente: não envie seus e-mails usando deliver_now mas sempre use deliver_later . Isso impede que os usuários esperem a entrega efetiva do email. Se você não possui sidekiq , sucker_punch ou qualquer outro em produção, basta usar config.active_job.queue_adapter = :async . E async ou inline para o ambiente de desenvolvimento.

Dada a seguinte configuração para o ambiente de teste, seus e-mails sempre serão enfileirados e nunca executados para entrega: isso evita que você zombe deles e pode verificar se eles estão enfileirados corretamente.

Nos testes, sempre divida o teste em dois: 1) Um teste de unidade para verificar se o email está enfileirado corretamente e com os parâmetros corretos 2) Um teste de unidade para o email para verificar se o assunto, remetente, destinatário e conteúdo estão corretos .

Dado o seguinte cenário:

class User
  after_update :send_email

  def send_email
    ReportMailer.update_mail(id).deliver_later
  end
end

Escreva um teste para verificar se o email está enfileirado corretamente:

include ActiveJob::TestHelper
expect { user.update(name: 'Hello') }.to have_enqueued_job(ActionMailer::DeliveryJob).with('ReportMailer', 'update_mail', 'deliver_now', user.id)

e escreva um teste separado para o seu email

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
  • Você testou exatamente se seu email foi colocado na fila e não um trabalho genérico.
  • Seu teste é rápido
  • Você não precisava zombar

Ao escrever um teste do sistema, sinta-se à vontade para decidir se realmente deseja enviar e-mails para ele, pois a velocidade não importa mais. Pessoalmente, gosto de configurar o seguinte:

RSpec.configure do |config|
  config.around(:each, :mailer) do |example|
    perform_enqueued_jobs do
      example.run
    end
  end
end

e atribua o atributo :mailer aos testes nos quais eu quero realmente enviar e-mails.

Para obter mais informações sobre como configurar corretamente seu email no Rails, leia este artigo: https://medium.com/@coorasse/the-correct-emails-configuration-in-rails-c1d8418c0bfd



Eu vim aqui procurando uma resposta para um teste completo, portanto, não apenas perguntando se há um e-mail aguardando para ser enviado, além disso, para seu destinatário, assunto ... etc.

Eu tenho uma solução, que vem here , mas com uma pequena mudança:

Como se diz, a parte curial é

mail = perform_enqueued_jobs { ActionMailer::DeliveryJob.perform_now(*enqueued_jobs.first[:args]) }

O problema é que os parâmetros que o remetente recebe, neste caso, são diferentes dos parâmetros que recebe na produção, na produção, se o primeiro parâmetro for um Modelo, agora no teste receberá um hash, portanto, trava.

enqueued_jobs.first[:args]
["UserMailer", "welcome_email", "deliver_now", {"_aj_globalid"=>"gid://forjartistica/User/1"}]

Portanto, se chamarmos o remetente como UserMailer.welcome_email(@user).deliver_later o remetente receberá na produção um usuário, mas no teste receberá {"_aj_globalid"=>"gid://forjartistica/User/1"}

Todos os comentários serão apreciados. A solução menos dolorosa que encontrei está mudando a maneira como chamo os encarregados do envio da correspondência, passando, o ID do modelo e não o modelo:

UserMailer.welcome_email(@user.id).deliver_later


Eu vim com a mesma dúvida e resolvi de uma maneira menos detalhada (linha única) inspirada nessa resposta

expect(ServiceMailer).to receive_message_chain(:new_user, :deliver_later).with(@user).with(no_args)

Observe que o último with(no_args) é essencial.

Mas, se você não se incomodar se deliver_later estiver sendo chamado, faça:

expect(ServiceMailer).to expect(:new_user).with(@user).and_call_original


Se você encontrar essa pergunta, mas estiver usando o ActiveJob em vez de simplesmente DelayedJob por conta própria e estiver usando o Rails 5, recomendo configurar o ActionMailer em config/environments/test.rb :

config.active_job.queue_adapter = :inline

(esse era o comportamento padrão anterior ao Rails 5)


Uma maneira simples é:

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

Usando o ActiveJob e o rspec 3.4+, você pode usar o have_enqueued_job assim:

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

Esta resposta é para teste de Rails, não para rspec ...

Se você estiver usando delivery_later assim:

 # 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 

Você pode verificar no seu teste se o email foi adicionado à fila:

 # 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 

Se você fizer isso, ficará surpreso com o teste que diz que assert_enqueued_jobs não está definido para nós usarmos.

Isso ocorre porque nosso teste é herdado do ActionController :: TestCase que, no momento da redação deste documento, não inclui o ActiveJob :: TestHelper.

Mas podemos corrigir rapidamente isso:

 # test/test_helper.rb class ActionController::TestCase include ActiveJob::TestHelper … end 

Referência: https://www.engineyard.com/blog/testing-async-emails-rails-42