如何用RSpec模拟单步数据库崩溃并测试事务回滚逻辑?
嘿,这个场景我之前也遇到过——想要精准模拟数据库操作到一半崩了的情况,而不是直接全库断开,对吧?不用改业务方法也能搞定,给你几个实用的方案:
方案1:Mock特定模型的创建方法,抛出数据库异常
最常用也最简洁的方式,就是利用RSpec的mock能力,拦截Notification的创建方法,让它抛出数据库相关的异常。这样整个事务会因为异常触发回滚,之前创建的Reservation和ReservedHour就会被销毁。
假设你的业务逻辑是包裹在事务里的(这是关键,没有事务的话回滚不生效),测试代码可以这么写:
describe "when database fails during notification creation" do let(:reservation_attrs) { { user_id: 1, status: "reserved" } } let(:reserved_hour_attrs) { { hour: "14:00", date: Date.today } } let(:notification_attrs) { { message: "Reservation confirmed" } } it "rolls back reservation and reserved_hour" do expect { ActiveRecord::Base.transaction do # 先创建前两个模型 reservation = Reservation.create!(reservation_attrs) ReservedHour.create!(reserved_hour_attrs.merge(reservation: reservation)) # 模拟创建notification时数据库崩溃,抛出真实的数据库异常 allow(Notification).to receive(:create!).and_raise(ActiveRecord::StatementInvalid, "PG::ConnectionBad: connection to server lost") # 执行创建notification的操作,触发异常 Notification.create!(notification_attrs.merge(reservation: reservation)) end }.to raise_error(ActiveRecord::StatementInvalid) # 验证前两个模型确实被回滚了 expect(Reservation.count).to eq(0) expect(ReservedHour.count).to eq(0) end end
如果你的业务逻辑封装在Service类里,那就更简单了,直接在调用Service方法前mockNotification.create!就行,不用手动写事务。
方案2:精准断开数据库连接(更贴近真实场景)
如果你想模拟更真实的数据库崩溃,而不是mock方法,可以在创建Notification的步骤前断开连接,测试后再恢复连接(一定要恢复,不然其他测试会炸):
describe "when database disconnects mid-process" do let(:reservation_attrs) { { user_id: 1, status: "reserved" } } let(:reserved_hour_attrs) { { hour: "14:00", date: Date.today } } let(:notification_attrs) { { message: "Reservation confirmed" } } after do # 测试结束后必须恢复数据库连接 ActiveRecord::Base.establish_connection(Rails.env.to_sym) end it "destroys reservation and reserved_hour" do reservation = nil reserved_hour = nil expect { ActiveRecord::Base.transaction do reservation = Reservation.create!(reservation_attrs) reserved_hour = ReservedHour.create!(reserved_hour_attrs.merge(reservation: reservation)) # 断开当前数据库连接 ActiveRecord::Base.remove_connection # 尝试创建notification,这时候会因为无连接抛出异常 Notification.create!(notification_attrs.merge(reservation: reservation)) end }.to raise_error(ActiveRecord::ConnectionNotEstablished) # 验证两个模型都被回滚了 expect(Reservation.find_by(id: reservation.id)).to be_nil expect(ReservedHour.find_by(id: reserved_hour.id)).to be_nil end end
这个方案的优势是完全模拟真实的数据库崩溃场景,但要注意after块里的连接恢复,否则后续测试会因为没有数据库连接全部失败。
关键前提:确保业务逻辑在事务中
不管用哪种方案,你的业务代码必须把Reservation、ReservedHour、Notification的创建逻辑包裹在同一个ActiveRecord事务里。如果这些操作是分开的、不在事务中,那即使Notification创建失败,前两个模型也不会自动回滚,测试自然也通不过。
比如你的业务代码应该类似这样:
def create_reservation_with_notification ActiveRecord::Base.transaction do reservation = Reservation.create!(...) ReservedHour.create!(reservation: reservation, ...) Notification.create!(reservation: reservation, ...) end end
这样只要事务中任何一步抛出异常,整个事务就会回滚,所有创建的记录都会被销毁。
内容的提问来源于stack exchange,提问作者Karol Selak
相关产品推荐
相关产品推荐

