简体   繁体   English

在 RSpec 中运行功能测试时,有没有办法强制记录不被破坏? (导轨 6)

[英]Is there a way I can force a record to not be destroyed when running a feature test in RSpec? (Rails 6)

For context, I have a controller method called delete_cars .对于上下文,我有一个名为delete_cars的 controller 方法。 Inside of the method, I call destroy_all on an ActiveRecord::Collection of Car s.在该方法内部,我在CarActiveRecord::Collection上调用了destroy_all Below the destroy_all , I call another method, get_car_nums_not_deleted_from_portal , which looks like the following:destroy_all下方,我调用了另一个方法get_car_nums_not_deleted_from_portal ,如下所示:

def get_car_nums_not_deleted_from_portal(cars_to_be_deleted)
  reloaded_cars = cars_to_be_deleted.reload
  car_nums = reloaded_cars.car_numbers

  if reloaded_cars.any?
    puts "Something went wrong. The following cars were not deleted from the portal: #{car_nums.join(', ')}"
  end

  car_nums
end

Here, I check to see if any cars were not deleted during the destroy_all transaction.在这里,我检查在destroy_all事务期间是否有任何汽车未被删除。 If there are any, I just add a puts message.如果有的话,我只是添加一条puts消息。 I also return the ActiveRecord::Collection whether there are any records or not, so the code to follow can handle it.我还返回ActiveRecord::Collection是否有任何记录,因此后面的代码可以处理它。

The goal with one of my feature tests is to mimic a user trying to delete three selected cars, but one fails to be deleted.我的一项功能测试的目标是模拟用户试图删除三辆选定的汽车,但其中一辆未能删除。 When this scenario occurs, I display a specific notice on the page stating:当这种情况发生时,我会在页面上显示一个特定的通知,说明:

'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#{some_car_numbers_go_here}"

How can I force just one record to fail when my code executes the destroy_all , WITHOUT adding extra code to my Car model (in the form of a before_destroy or something similar)?当我的代码执行destroy_all时,如何在不向我的Car model 添加额外代码的情况下(以before_destroy或类似形式)强制仅记录一条记录失败? I've tried using a spy, but the issue is, when it's created, it's not a real record in the DB, so my query:我试过使用间谍,但问题是,当它被创建时,它不是数据库中的真实记录,所以我的查询:

cars_to_be_deleted = Car.where(id: params[:car_ids].split(',').collect { |id| id.to_i })

doesn't include it.不包括它。

For even more context, here's the test code:有关更多上下文,这是测试代码:

context 'when at least one car is not deleted, but the rest are' do
  it "should display a message stating 'Some selected cars have been successfully...' and list out the cars that were not deleted" 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)

    find_field("cars_to_edit[#{Car.first.id}]").click
    find_field("cars_to_edit[#{Car.second.id}]").click
    find_field("cars_to_edit[#{Car.third.id}]").click
    click_button('Delete cars')

    cars_to_be_deleted = Car.where(id: Car.first(3).map(&:id)).ids
    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#{@first_three_cars_car_numbers[0]}".upcase
                    )
    expect(Car.count).to eq(98)
    expect(Car.where(id: cars_to_be_deleted).length).to eq(1)
  end
end

Any help with this would be greatly appreciated.对此的任何帮助将不胜感激。 It's becoming quite frustrating lol.它变得非常令人沮丧大声笑。

One way to "mock" not deleting a record for a test could be to use the block version of .to receive to return a falsy value. “模拟”不删除测试记录的一种方法是使用block版本的falsy .to receive返回一个虚假值。

The argument for the block is the instance of the record that would be :destroy ed.该块的参数是将被:destroy编辑的记录的实例。

Since we have this instance, we can check for an arbitrary record to be "not destroyed" and have the block return nil , which would indicate a "failure" from the :destroy method.由于我们有这个实例,我们可以检查任意记录是否“未被销毁”并让块返回nil ,这表明:destroy方法“失败”。

In this example, we check for the record of the first Car record in the database and return nil if it is.在这个例子中,我们检查数据库中第一条Car记录的记录,如果是则返回nil If it is not the first record, we use the :delete method, as to not cause an infinite loop in the test (the test would keep calling the mock :destroy ).如果它不是第一条记录,我们使用:delete方法,以免在测试中导致无限循环(测试将继续调用 mock :destroy )。

allow_any_instance_of(Car).to receive(:destroy) { |car|
      # use car.delete to prevent infinite loop with the mocked :destroy method
      if car.id != Car.first.id
        car.delete
      end
      # this will return `nil`, which means failure from the :destroy method
}

You could create a method that accepts a list of records and decide which one you want to :destroy for more accurate testing!您可以创建一个接受记录列表的方法,并决定要:destroy哪个记录以进行更准确的测试!

I am sure there are other ways to work around this, but this is the best we have found so far:)我相信还有其他方法可以解决这个问题,但这是迄今为止我们发现的最好的方法:)

If there is a specific reason why the deletion might fail you can simulate that case.如果有特定原因导致删除失败,您可以模拟这种情况。

Say you have a RaceResult record that must always refer to a valid Car and you have a DB constraint enforcing this (in Postgres: ON DELETE RESTRICT ).假设您有一条RaceResult记录,该记录必须始终引用有效的Car并且您有一个强制执行此操作的数据库约束(在 Postgres 中: ON DELETE RESTRICT )。 You could write a test that creates the RaceResult records for some of your Car records:您可以编写一个测试,为您的一些Car记录创建RaceResult记录:

it 'Cars prevented from deletion are reported` do
  ...
  do_not_delete_cars = Car.where(id: Car.first(3).map(&:id)).ids
  do_not_delete_cars.each { |car| RaceResult.create(car: car, ...) }

  click_button('Yes')

  expect(page).to have_text(...
end

Another option would be to use some knowledge of how your controller interacts with the model:另一种选择是使用一些关于您的 controller 如何与 model 交互的知识:

  allow(Car).to receive(:destroy_list_of_cars).with(1,2,3).and_return(false) # or whatever your method would return

This would not actually run the destroy_list_of_cars method, so all the records would still be there in the DB.这实际上不会运行destroy_list_of_cars方法,因此所有记录仍会存在于数据库中。 Then you can expect error messages for each of your selected records.然后,您可以预期每个选定记录的错误消息。

Or since destroy_all calls each record's destroy method, you could mock that method:或者由于destroy_all调用每条记录的destroy方法,您可以模拟该方法:

allow_any_instance_of('Car').to receive(:destroy).and_return(false) # simulates a callback halting things

allow_any_instance_of makes tests brittle however. allow_any_instance_of使测试变得脆弱。

Finally, you could consider just not anticipating problems before they exist (maybe you don't even need the bulk delete page to be this helpful?).最后,您可以考虑在问题出现之前不要预料到它们(也许您甚至不需要批量删除页面来提供这么大的帮助?)。 If your users see a more generic error, is there a page they could filter to verify for themselves what might still be there?如果您的用户看到更普遍的错误,是否有他们可以过滤的页面以自行验证可能仍然存在的内容? (there's a lot of factors to consider here, it depends on the importance of the feature to the business and what sort of things could go wrong if the data is inconsistent). (这里有很多因素需要考虑,这取决于该功能对业务的重要性以及如果数据不一致会导致什么样的错误 go )。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM