如何在Rspec中捕获ActiveRecord::NotNullViolation异常?
expect { job.perform_now(item_id) }.to raise_error(ActiveRecord::NotNullViolation)
但测试失败,报错信息如下: > ActiveRecord::NotNullViolation: > Mysql2::Error: Column 'calendar_id' cannot be null 实际场景是:job会更新一个**无非空约束**的model,将`calendar_id`设为NULL,同时触发回调执行以下代码: ```ruby another_model.update_all(calendar_id: calendar_id)
而another_model的calendar_id字段设有非空约束,因此update_all语句执行失败,这符合预期,但Rspec为何无法捕获该异常?
原因及解决办法
核心原因
问题出在Job框架的异常处理机制上:比如Sidekiq这类常用Job框架,默认会捕获Job执行过程中抛出的异常,要么记录日志、要么触发重试逻辑,不会把异常直接冒泡到Rspec的expect块里,导致测试抓不到这个异常。
另外还有一种可能:如果Job执行时开启了数据库事务,异常在事务内抛出后被框架捕获并回滚,但没重新抛到外层,Rspec也感知不到。
具体解决办法
绕过Job框架的异常包装
不用perform_now,直接调用Job的perform方法,避开框架的异常处理逻辑:expect { job.perform(item_id) }.to raise_error(ActiveRecord::NotNullViolation)要是用的是Sidekiq,也可以在测试中用fake模式运行:
Sidekiq::Testing.fake! do expect { job.perform_now(item_id) }.to raise_error(ActiveRecord::NotNullViolation) end单独测试回调逻辑
既然异常是回调里的update_all抛出来的,直接测试回调本身更直接,不用依赖Job执行:expect { model.run_callback(:after_update) }.to raise_error(ActiveRecord::NotNullViolation)检查事务边界
如果Job或回调里用了事务,确认异常没被事务的rescue块吞掉。可以临时关闭Rspec的事务 fixtures:# 在测试文件开头或spec_helper.rb中设置 RSpec.configure do |config| config.use_transactional_fixtures = false end
内容的提问来源于stack exchange,提问作者53c
相关产品推荐
相关产品推荐

