ActiveRecord:模拟 has_many 关系调用

ActiveRecord: Mock has_many relation call

我对测试还很陌生,所以我一直在努力使用正确的语法,尤其是关于 mocks.

我想在 cars_controller.rb

中测试我的 destroy 动作
def destroy
  if current_user.cars.exists?(params[:id])
    car = current_user.cars.find(params[:id])

    # only destroy the car if it has no bookings
    car.destroy unless car.bookings.exists?
  end

  redirect_to user_cars_path(current_user)
end

当没有与汽车相关的预订时,测试案例相当容易。

describe CarsController, type: :controller do

  let(:user) { create(:user_with_car) }
  before { login_user(user) }

  describe "DELETE #destroy" do
    let(:car) { user.cars.first }

    context "when the car has no bookings associated to it" do
      it "destroys the requested car" do
        expect {
          delete :destroy, user_id: user.id, id: car.id
        }.to change(user.cars, :count).by(-1)
      end
    end

但是这个测试让我抓狂:

    context "when the car has bookings associated to it" do
      it "does not destroy the requested car" do

        ##### This line fails miserably
        allow(car).to receive_message_chain(:bookings) { [ Booking.new ]}

        expect {
          delete :destroy, user_id: user.id, id: car.id
        }.to change(user.cars, :count).by(0)
      end
    end
  end
end

我不想在数据库中创建预订并将它们与汽车相关联。据我了解,建议模拟这些预订,因为它们没有用

旁边:

allow(car).to receive_message_chain(:bookings) { [ Booking.new ]}

我曾多次尝试使用其他语法',但都失败了。我什至尝试使用 rpsec-mocks 旧语法:stub(...).

我该如何完成?

这不起作用的原因是删除操作加载了它自己的 car 版本 - 它没有使用您在本地声明给规范的局部变量。因此,您添加到局部变量的任何存根实际上不会存在于控制器操作内的 car 的全新副本中。

有几种解决方法。

  1. 一个是把你的控制器拿走的车弄下来。
  2. 另一种是打掉any_instance_of(Car)
  3. 第三种是为您拥有的汽车实际设置预订...

这些选项之间的区别是在与代码内部紧密纠缠(即更难维护)、速度 运行 或实际测试代码的所有方面之间进行权衡。

第三个确保一切正常(你有一辆真实预订的真实汽车),但速度较慢,因为它在数据库中设置实际模型......这就是你想要得到的东西过去。

第一个和第二个由你决定。我个人有一种 "ick" 感觉当你测试一个控制器时,发现汽车是你希望测试的一部分......

另外 - 它只会找到您之前设置的汽车,因此您不妨在任何实例上做一个存根。

所以:

expect_any_instance_of(Car).to receive(:bookings).and_return([ Booking.new ])`

可能会成功。

Rspec any_instance_of doco