ActiveRecord:模拟has_many关系调用

时间:2015-05-04 07:26:08

标签: ruby-on-rails activerecord rspec mocking

我对测试很新,所以我一直在努力使用正确的语法,特别是关于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(...)

我将如何做到这一点?

1 个答案:

答案 0 :(得分:1)

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

有很多方法可以解决这个问题。

  1. 一个是剔除控制器取出的汽车。
  2. 另一种方法是删除any_instance_of(Car)
  3. 第三是真正为你的车预订......
  4. 这些选项之间的区别在于与代码内部紧密纠缠(即难以维护),运行速度或实际测试代码的所有方面之间的权衡。

    第三个确保一切都真的有效(你有一辆真正预订的真车),但速度较慢,因为它在数据库中设置了实际模型......这就是什么你顽固地过去了。

    第一个和第二个由你决定。我个人有一个" ick"当你在测试控制器的时候发现汽车是你希望进行测试的一部分的时候感觉有点难以找到......

    加上 - 它只能找到你之前设置的汽车,所以你不妨在任何一个实例上做一个存根。

    SO:

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

    可能会做到这一点。

    Rspec any_instance_of doco