ActiverEcord:模拟HAS_MANY关系呼叫



我是测试的新手,所以我一直在努力使用正确的语法,尤其是关于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. 第三个是实际设置您拥有的汽车的预订...

这些选项之间的区别是与代码内部纠缠(即更难维护),运行速度或实际测试代码的所有方面之间的折衷。

第三个要确保一切都真正起作用(您有一辆真正的预订汽车),但是它较慢,因为它在DB中设置了实际模型...这就是您要过去的固执。

第一和第二取决于您。我个人对测试控制器进行测试时有一种" iCk"的感觉,其中找到汽车是您希望正在测试的一部分...

plus-只能找到您之前设置的汽车,因此您不妨在任何情况下进行存根。

so:

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

可能会解决这个问题。

rspec any_instance_of doco

最新更新