I have this logic where I send a welcome email to the user when it is created.
User.rb
class User < ApplicationRecord
# Callbacks
after_create :send_registration_email
private
def send_registration_email
UserConfirmationMailer.with(user: self).user_registration_email.deliver_later
end
end
I tried testing it in user_spec.rb
:
describe "#save" do
subject { create :valid_user }
context "when user is created" do
it "receives welcome email" do
mail = double('Mail')
expect(UserConfirmationMailer).to receive(:user_registration_email).with(user: subject).and_return(mail)
expect(mail).to receive(:deliver_later)
end
end
end
But it is not working. I get this error:
Failure/Error: expect(UserConfirmationMailer).to receive(:user_registration_email).with(user: subject).and_return(mail)
(UserConfirmationMailer (class)).user_registration_email({:user=>#<User id: 5, email: "factory10@test.io", created_at: "2023-02-18 01:09:34.878424000 +0000", ...878424000 +0000", jti: "2163d284-1349-4e48-8a2a-1b52b578921c", username: "jose_test10", icon_id: 5>})
expected: 1 time with arguments: ({:user=>#<User id: 5, email: "factory10@test.io", created_at: "2023-02-18 01:09:34.878424000 +0000", ...878424000 +0000", jti: "2163d284-1349-4e48-8a2a-1b52b578921c", username: "jose_test10", icon_id: 5>})
received: 0 times
Am I doing something wrong when testing the action of sending the email in a callback?
PD.
My environment/test.rb
config is as follows:
config.action_mailer.delivery_method = :test
config.active_job.queue_adapter = :test
config.action_mailer.default_url_options = { :host => "http://localhost:3000" }
Even if I change config.active_job.queue_adapter
to :test
following this, I get the same error.
Another way I am trying to do it is like this:
expect { FactoryBot.create(:valid_user) }.to have_enqueued_job(ActionMailer::MailDeliveryJob).with('UserConfirmationMailer', 'user_registration_email', 'deliver_now', subject)
But then subject
and the user created in FactoryBot.create(:valid_user)
is different..
Any ideas are welcome. Thank you!
The stubbing in the question doesn't work because it doesn't stub the chain of methods in the same order then they are called in the implementation.
When you want to stub this method chain
UserConfirmationMailer.with(user: self).user_registration_email.deliver_later
then you have to first stub the with
call, then the user_registration_email
and last the deliver_later
.
describe '#save' do
subject(:user) { build(:valid_user) }
let(:parameterized_mailer) { instance_double('ActionMailer::Parameterized::Mailer') }
let(:parameterized_message) { instance_double('ActionMailer::Parameterized::MessageDelivery') }
before do
allow(UserConfirmationMailer).to receive(:with).and_return(parameterized_mailer)
allow(parameterized_mailer).to receive(:user_registration_email).and_return(parameterized_message)
allow(parameterized_message).to receive(:deliver_later)
end
context 'when user is created' do
it 'sends a welcome email' do
user.save!
expect(UserConfirmationMailer).to have_received(:with).with(user: user)
expect(parameterized_mailer).to have_received(:user_registration_email)
expect(parameterized_message).to have_received(:deliver_later)
end
end
end
Note: I am not sure if using instance_double
will work in this case, because the Parameterized
uses method_missing
internally. Although instance_double
is usually preferred, you might need to use double
instead.