ActionMailer::Base.deliveries.size
to have changed by 1, but was changed by 2'after_create
hookverfiy_mailer.rb
# frozen_string_literal: true
class VerifyMailer < ApplicationMailer
def verification_email(user)
@user = user
mail(to: user.email, subject: 'verification code')
end
end
verify_mailer_spec.rb
# frozen_string_literal: true
require 'rails_helper'
# include ActiveJob::TestHelper
RSpec.describe VerifyMailer, type: :mailer do
let(:user) { create(:user) }
let(:mail) { VerifyMailer.verification_email(user).deliver_now }
it 'renders the receiver email' do
expect(mail.to).to eq([user.email])
end
it 'renders the subject' do
expect(mail.subject).to eq('verification code')
end
it 'renders the sender email' do
expect(mail.from).to eq(['[email protected]'])
end
end
user.rb
# frozen_string_literal: true
class User < ApplicationRecord
after_create :send_user_otp
attr_writer :login
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable ,
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, authentication_keys: [:username]
has_many :payments
validates :email, :username, presence: true, uniqueness: true
validates :username, format: { with: /\A[a-zA-Z0-9_.]*\z/,
message: ' only allow letter, number, underscore and punctuation marks' }
# Loggin with user_name
def login
@login || username
end
def send_user_otp
unverify!
otp = generate_codes
update_column(:otp_code, otp)
VerifyMailer.verification_email(self).deliver_now
touch(:otp_sent_at)
end
def unverify!
update_column(:verified, false)
end
def generate_codes
loop do
code = rand(0o00000..999_999).to_s
break code unless code.length != 6
end
end
end
I have added config.action_mailer.delivery_method = :test to enviroment/test.rb
am also using factory_bot_rails to create user
FactoryBot.define do
factory :user do
sequence(:email) { |n| "person#{n}@example.com" }
sequence(:username) { |n| "user#{n}" }
password { '!Mutebi2' }
password_confirmation { '!Mutebi2' }
end
end
As in most cases, the computer is doing exactly what you told it to do:
let(:user) { create(:user) }
let(:mail) { VerifyMailer.verification_email(user).deliver_now }
Creating the user
with FactoryBot
triggers any/all callbacks:
class User < ApplicationRecord
after_create :send_user_otp
So create(:user)
calls :send_user_otp
after it's created.
Then you send another email with VerifyMailer.verification_email(user).deliver_now
Hence why ActionMailer::Base.deliveries.size
changes by 2.
In your VerifyMailer
tests, you are testing the attributes of the email, so you don't need the user to actually be saved or the email to actually be sent.
If you need an instance of the email, just remove deliver_now
. And you can build
instead of create
the user
:
RSpec.describe VerifyMailer, type: :mailer do
let(:user) { build(:user) }
let(:mail) { VerifyMailer.verification_email(user) }
it 'renders the receiver email' do
expect(mail.to).to eq([user.email])
end
it 'renders the subject' do
expect(mail.subject).to eq('verification code')
end
it 'renders the sender email' do
expect(mail.from).to eq(['[email protected]'])
end
end
In your User
tests, you can verify that creating a user also creates the email:
RSpec.describe User, type: :model do
let(:user) { build(:user) }
describe "after create" do
it "calls #send_user_otp" do
expect(user).to receive(:send_user_otp)
end
end
describe "#send_user_otp" do
it "calls VerifyMailer.verification_email" do
expect(VerifyMailer).to receive(:verification_email).with(user)
user.send(:send_user_otp)
end
end
end
Note that I'm not actually testing ActionMailer::Base.deliveries.size
at all. That's a service or request test, which should be based on a full implementation, e.g.:
When this form is filled out:
Your User
model isn't responsible for VerifyMailer
, ApplicationMailer
and any other code that handles delver_now
, it's only responsible for calling VerifyMailer
and handing it the user
instance.
And VerifyMailer
isn't responsible for handling delivery, it creates the email and passes it off to ActionMailer
.
Keep your tests focused on the responsibilities of your code, it will make them less brittle.
If you change something in your ActionMailer
setup, you don't want User
tests to start to fail.