使用Rspec和factory_bot测试邮件器时遇发送数量不符错误求助
Rspec + FactoryBot 邮件器测试报错:预期邮件发送数变化1,实际变化2
我是测试新手,使用Rspec和factory_bot测试邮件器时遇到问题,运行测试时出现错误:
expected ActionMailer::Base.deliveries.size to have changed by 1, but was changed by 2
邮件由User模型的after_create钩子触发,实际代码运行正常但测试失败,相关代码如下:
相关代码
verify_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(['mutebigod10@gmail.com']) 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
FactoryBot 配置
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
已在environment/test.rb中添加config.action_mailer.delivery_method = :test配置。
问题原因
测试中邮件发送数翻倍的核心原因:
- 执行
create(:user)时,User模型的after_create钩子会调用send_user_otp方法,自动发送1封验证邮件。 - 测试代码里又手动调用
VerifyMailer.verification_email(user).deliver_now,再次发送1封邮件。 - 两次发送导致
ActionMailer::Base.deliveries的数量变化了2,和预期的1不符。
解决方法
有两种常见修复方式,可根据测试需求选择:
方式1:跳过User的after_create钩子(单独验证邮件器)
如果测试仅需验证邮件器的内容格式,不需要触发模型的业务逻辑,可以在创建用户时跳过回调:
方案1-1:直接跳过所有回调
修改verify_mailer_spec.rb中的user定义:
let(:user) { create(:user, without_protection: true) }
方案1-2:精准跳过指定回调
先给FactoryBot添加一个trait:
FactoryBot.define do factory :user do # ... 原有配置 ... trait :skip_verification_email do after(:build) do |user| user.skip_callback(:create, :after, :send_user_otp) end end end end
然后在测试中使用该trait:
let(:user) { create(:user, :skip_verification_email) }
方式2:利用钩子触发的邮件进行验证(贴合业务场景)
如果测试目的是验证"用户创建后邮件是否正确发送",可以直接检查钩子触发的邮件,无需手动调用邮件器:
修改verify_mailer_spec.rb:
RSpec.describe VerifyMailer, type: :mailer do before do ActionMailer::Base.deliveries.clear # 清空历史邮件记录,避免干扰当前测试 end it 'sends correct verification email after user creation' do user = create(:user) mail = ActionMailer::Base.deliveries.last expect(mail.to).to eq([user.email]) expect(mail.subject).to eq('verification code') expect(mail.from).to eq(['mutebigod10@gmail.com']) end end
额外建议
- 所有邮件测试前,建议清空
ActionMailer::Base.deliveries,避免之前的测试用例残留数据影响结果。 - 如果后续改用
deliver_later异步发送邮件,需要在测试中加入perform_enqueued_jobs来触发任务(当前用deliver_now无需此操作)。
内容的提问来源于stack exchange,提问作者mutebi godfrey
相关产品推荐
相关产品推荐

