批量运行Devise相关系统测试失败,单独运行正常(Devise::Mailer报错)
在基于Ruby on Rails 7、集成Devise的应用中使用RSpec进行系统测试时,遇到以下异常现象:
- 单独运行每个系统测试用例时全部通过;
- 批量运行所有系统测试时,所有与Devise相关(确认、密码、注册、会话、解锁)的测试都会报错:
ActionView::Template::Error: undefined local variable or method `controller' for #Devise::Mailer:0x0000000007fb98
- 仅重新批量运行这些失败的测试时,它们又全部通过。
以下是用户注册相关的失败测试示例:
RSpec.describe 'Registrations: signing up user', :type => :system do describe 'sign up user' do before(:each) do @regular_user = FactoryBot.build(:regular_user) visit new_user_registration_path end it 'should sign up user', :js => true do within("#form_id") do fill_in_sign_up_user_form(@regular_user.username, @regular_user.email, @regular_user.password) end expect(page).to have_current_path(root_path) # ... end end end
请问Devise::Mailer在批量运行时出现该问题的原因是什么,如何解决?
核心原因
这个问题本质是Devise邮件器(Devise::Mailer)在批量测试时丢失controller上下文,或是自定义Devise邮件模板错误引用了controller变量——Devise::Mailer继承自ActionMailer::Base,本身并不具备controller实例方法,该变量仅存在于控制器上下文当中。
批量测试触发、单独/重跑失败测试又恢复正常,通常源于:
- 测试间的状态残留,某一测试修改了邮件器全局配置/上下文,影响后续测试;
- 并行测试(若开启)的进程间状态冲突;
- 特定Devise版本存在系统测试场景下的上下文丢失bug。
解决方案
1. 检查并修复自定义Devise邮件模板
如果项目修改过Devise的邮件模板(如app/views/devise/mailer/confirmation_instructions.html.erb等),排查是否存在controller变量引用:
- 错误示例:
<%= controller.root_url %> - 替换为直接调用路由辅助方法:
<%= root_url %>(邮件模板可直接使用路由辅助方法,无需依赖controller)
2. 确保测试环境邮件配置完整
在config/environments/test.rb中确认ActionMailer的默认URL配置,避免邮件模板生成时依赖上下文:
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } config.action_mailer.delivery_method = :test # 测试环境默认配置,确保未被修改
3. 强化测试间的状态隔离
在RSpec配置文件(spec/rails_helper.rb或spec/spec_helper.rb)中添加钩子,确保每个系统测试后清理邮件器状态:
RSpec.configure do |config| config.after(:each, type: :system) do # 清空已发送邮件队列 ActionMailer::Base.deliveries.clear # 若有自定义邮件器的全局状态,在此同步重置 end end
4. 禁用并行测试(若开启)
如果使用了parallel_tests等并行测试工具,暂时禁用并行运行,验证是否为进程间状态冲突导致的问题。若禁用后恢复正常,再针对并行环境调整邮件器隔离配置。
5. 更新Devise到最新稳定版
部分旧版本Devise在系统测试批量运行时存在上下文丢失bug,更新到最新稳定版(参考Devise官方版本日志)可能直接解决问题。
内容的提问来源于stack exchange,提问作者user502052

