【问题标题】:ActiveRecord: Mock has_many relation callActiveRecord:模拟 has_many 关系调用
【发布时间】:2015-05-04 07:26:07
【问题描述】:

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

我将如何做到这一点?

【问题讨论】:

  • 除非您确实有消息链,否则您可能不需要接收消息链。这里只有一个,所以你应该可以这样做:allow(car).to receive(:bookings) { [ Booking.new ]}?
  • 另外,我不知道为什么会阻塞。怎么样:allow(car).to receive(:bookings).and_return([ Booking.new]) 因为它更明确

标签: ruby-on-rails activerecord rspec mocking


【解决方案1】:

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

有几种方法可以解决这个问题。

  1. 一种方法是剔除控制器获取的汽车。
  2. 另一个是存根any_instance_of(Car)
  3. 第三个是为您拥有的汽车实际设置预订...

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

第三个确保一切正常(您拥有一辆真正的汽车并进行了真正的预订),但速度较慢,因为它在数据库中设置了实际模型……这就是您要克服的问题。

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

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

所以:

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

可能会成功。

Rspecany_instance_of doco

【讨论】:

  • 是的 - 它们不是问题的解决方案......它们只是对更标准化使用的建议......我正在研究解决方案;)
  • 是的 - 这是对需要做什么的概括描述 - 以上应该是实际代码(除非拼写错误等)
  • 太棒了,它成功了,是的,现在它失败了,因为它在数组上调用exists?,但我会找到解决方案。感谢您的帮助塔林!真的很感激。我还要删除我上面的​​ cmets,它们很杂乱。
  • 啊当然。我会使用.present?,这更加有条理:)
  • 是的,确实是这样,真的更无耻^^,
猜你喜欢
  • 1970-01-01
  • 2015-07-03
  • 1970-01-01
  • 1970-01-01
  • 2020-10-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多