Rails测试疑问:ActiveMailer发送后delivery_method.settings被清空?
问题:测试ActiveMailer动态SMTP设置时,发送后无法获取配置
我需要验证UserMailer是否按照租户指定的SMTP服务器发送邮件,在UserMailer中通过before_action钩子动态设置SMTP配置:
class UserMailer < ApplicationMailer before_action :set_user before_action :set_smtp_settings # ... 其他代码 ... def set_smtp_settings tenant_email_provider = @user.tenant.email_providers.where(provider_type: 'smtp').take tenant_smtp_settings = (tenant_email_provider&.settings || {}).with_indifferent_access current_settings = (message.delivery_method.settings || {} ).with_indifferent_access new_settings = current_settings.merge(tenant_smtp_settings) raise "No server address to send to" if new_settings[:address].nil? message.delivery_method.settings = new_settings end end
单元测试代码如下:
test 'that the settings are according to a mailer' do expected_settings = email_provider.settings.with_indifferent_access message = @mailer.account_activation_confirm sent_message = message.deliver_now actual_settings = sent_message.delivery_method.settings.with_indifferent_access intersecting_keys = expected_settings.keys.map(&:to_sym) | actual_settings.keys assert intersecting_keys.include?(:address) intersecting_keys.each do |setting| expected = expected_settings[setting].to_s actual = actual_settings[setting].to_s assert_equal(expected, actual, "smtp value of setting '#{setting}' do not match (#{expected} != #{actual})") end end
测试失败,报错信息:
Failure: DeliverSettingsTest#test_that_the_settings_are_according_to_a_mailer [/opt/xxx-web/test/mailers/deliver_settings_test.rb:25]: smtp value of setting 'address' do not match (mail.hello.com != ). --- expected +++ actual @@ -1 +1,3 @@ -"mail.hello.com" +# encoding: US-ASCII +# valid: true +""
通过调试发现:
- 在
set_smtp_settings钩子中,message.delivery_method.settings已正确填充租户的SMTP配置:(ruby) message.delivery_method.settings {"address"=>"mail.hello.com", "port"=>25, "domain"=>"localhost.localdomain", "user_name"=>"prius", "password"=>"bunch_tr1k3s!", "authentication"=>nil, "enable_starttls"=>nil, "enable_starttls_auto"=>true, "openssl_verify_mode"=>nil, "ssl"=>nil, "tls"=>nil, "open_timeout"=>5, "read_timeout"=>5} - 但在测试代码中,
sent_message.delivery_method.settings是空哈希:(ruby) actual_settings {}
疑问:是否是ActiveMailer库或测试桩在邮件发送后清空了delivery_method.settings的内容?
分析原因
确实是测试环境下ActiveMailer的默认行为导致的:
- 测试环境默认使用
:test投递方式:测试模式中,ActiveMailer不会真正通过SMTP发送邮件,而是用ActionMailer::DeliveryMethods::Test这个桩实现,仅把邮件存入内存的ActionMailer::Base.deliveries数组,不会保留原SMTP投递方式的配置。 - 发送后返回的
sent_message已切换为测试桩:调用deliver_now后,邮件对象的投递方式会被替换为测试用的实现,你之前设置的SMTP配置不会保留在返回的sent_message中。
解决方案
可以通过两种方式调整测试逻辑:
方案1:发送前直接验证邮件对象的配置
无需等待发送,直接在调用deliver_now前检查message的投递配置:
test 'that the settings are according to a mailer' do expected_settings = email_provider.settings.with_indifferent_access message = @mailer.account_activation_confirm # 发送前验证配置 actual_settings = message.delivery_method.settings.with_indifferent_access intersecting_keys = expected_settings.keys.map(&:to_sym) | actual_settings.keys assert intersecting_keys.include?(:address) intersecting_keys.each do |setting| expected = expected_settings[setting].to_s actual = actual_settings[setting].to_s assert_equal(expected, actual, "smtp value of setting '#{setting}' do not match (#{expected} != #{actual})") end # 可选:若需确保邮件能正常发送,保留发送操作 message.deliver_now end
方案2:用Mock验证SMTP配置被正确应用
如果要验证配置确实被应用到发送流程,可借助Minitest的Mock功能拦截SMTP初始化:
test 'that the settings are according to a mailer' do expected_settings = email_provider.settings.with_indifferent_access # 预期的完整配置(含默认值) expected_full_settings = message.delivery_method.settings.with_indifferent_access.merge(expected_settings) # Mock SMTP投递方式,验证初始化时使用了正确配置 smtp_mock = Minitest::Mock.new smtp_mock.expect(:deliver!, nil, [instance_of(Mail::Message)]) smtp_mock.expect(:settings=, nil, [expected_full_settings]) # 替换邮件对象的投递方式为mock message = @mailer.account_activation_confirm message.delivery_method(smtp_mock) message.deliver_now # 验证mock的预期调用是否执行 smtp_mock.verify end
内容的提问来源于stack exchange,提问作者Julien Lamarche
相关产品推荐
相关产品推荐

