Rails 6下如何在RSpec功能测试中阻止单条Car记录销毁?
解决方案
要在不修改Car模型代码的前提下,模拟单条记录删除失败的场景,核心思路是在测试中对目标Car实例的destroy方法进行Stub,强制它返回失败结果,这样destroy_all执行时只会跳过该记录,其他记录正常删除。
具体实现步骤
- 在测试中先定位要设置删除失败的Car实例
- 在触发删除操作(点击"Yes"按钮)之前,Stub该实例的
destroy方法,让它返回false(或模拟抛出异常,视业务逻辑而定) - 执行删除操作后验证结果
调整后的测试代码示例
context 'when at least one car is not deleted, but the rest are' do it "should display the partial success message and list undeleted cars" do expect(Car.count).to eq(100) visit bulk_edit_cars_path select(@location.name.upcase, from: 'Location') select(@track.name.upcase, from: 'Track') click_button("Search".upcase) # 选定要测试的3辆车,标记其中1辆为删除失败目标 failed_car = Car.first selected_cars = [failed_car, Car.second, Car.third] # 勾选车辆复选框 selected_cars.each do |car| find_field("cars_to_edit[#{car.id}]").click end click_button('Delete cars') # Stub失败车辆的destroy方法,强制返回false(不删除) allow(failed_car).to receive(:destroy).and_return(false) # 若需要模拟数据库级别的删除失败,可改用抛出异常: # allow(failed_car).to receive(:destroy).and_raise(ActiveRecord::RecordNotDestroyed) # 确认删除 click_button('Yes') # 验证页面提示和数据库状态 expect(page).to have_text( 'Some selected cars have been successfully deleted from the portal, however, some have not. The ' "following cars have not been deleted from the portal:\n\n#{failed_car.car_number}".upcase ) expect(Car.count).to eq(98) expect(Car.where(id: selected_cars.map(&:id)).count).to eq(1) end end
原理说明
destroy_all会遍历集合中的每个实例,依次调用它们的destroy方法- 我们Stub的是从数据库中取出的真实Car实例,所以控制器中
Car.where(id: params[:car_ids])的查询能正常包含该记录,解决了之前用Spy导致的记录不存在问题 - 当目标实例的
destroy被Stub返回false时,这条记录不会被删除,其他实例的destroy会正常执行,实现"部分成功"的场景
注意事项
- 如果你的
destroy_all是在全局事务中执行的,抛出异常可能导致整个事务回滚(所有记录都不删除),这种情况下建议用and_return(false)而不是抛出异常 - 确保Stub操作在触发删除(点击"Yes")之前执行,否则无法拦截到
destroy调用
内容的提问来源于stack exchange,提问作者JoshingYou
相关产品推荐
相关产品推荐

