Ruby 困惑于如何使用模拟和存根

Ruby 困惑于如何使用模拟和存根,ruby,rspec,Ruby,Rspec,我有一个等级和一个规格 class Store def activate(product_klass, product_id) product = product_klass.find(product_id) if product.inactive? product.update_attribute :active, true end end end describe Store do it "should activate an inactiv

我有一个等级和一个规格

class Store
  def activate(product_klass, product_id)
    product = product_klass.find(product_id)
    if product.inactive?
      product.update_attribute :active, true
    end
  end
end

describe Store do
  it "should activate an inactive product" do
    product = mock
    product.stub(:inactive?).and_return(true)    
    store = Store.new
    store.activate(22) # 
    product.should be_active
  end
end
运行规范失败。我得到:

Mock received unexpected message :find_by_id with (1)
为了满足这一点,我在行
store.activate(产品,22)
之前添加了
product.should.\u receive(:find\u by\u id.).with(1.)和
。(这样做似乎是错误的,因为我不希望我的测试对我正在测试的方法的内部了解太多)

再次运行规范,我得到失败,以下行返回
false
,而不是预期的
true

product.should be_active
因此,它返回
false
,因为
product.update\u属性:active,true
并没有真正将
active
设置为
true
:它只是被模拟吸收了

我有很多问题。一个人是如何进行选择的?我应该如何测试这个呢?我是否正确使用模拟和存根


非常感谢您的帮助。

我认为激活逻辑根本不属于
存储
。如果它是在
产品中声明的
,那么测试对我来说会自然得多:

class Product < ActiveRecord::Base
  def activate
    if inactive?
      update_attribute :active, true
    end
  end
end

describe Product do
  it "should activate an inactive product" do
    product = Product.new
    product.activate 
    product.should be_active
  end
end

我同意@padde的观点,即产品激活应该在
产品
模型上,因此:

class Product < ActiveRecord::Base
  def activate
    if inactive?
      update_attribute :active, true
    end
  end
end
以及
存储
测试:

describe Store do
  context "when activating a product" do
    let(:product)       { mock }
    let(:store)         { Store.new }

    before do
      product_klass = double                 # Stub the product class, don't mock
      product_klass.stub(:find) { product }  # We want to test product here, not the class
      store.activate(product_klass, 22)
    end

    subject { product }                      # product is the subject again

    it { should_receive(:activate) }         # it should receive the activate message
  end
end
我删除了对
产品的期望,因为在本例中,这并不是您真正感兴趣的测试内容。你可能会喜欢把它作为一个单独的测试


使用
let
subject
context
以标准方式安排您的测试,并允许rspec做一些整洁的事情,如生成类的人性化文档。有关rspec最佳实践的更多信息,请参阅。

好的,我了解要点。但是我不喜欢这样的想法,我必须在那里有一行
product\u klass.should\u receive(:find).和
。这似乎太过分了,使规范变得脆弱?。如果我要更改产品的获取方式,那么我也必须更改规范。对于方法的逻辑来说,记录的获取方式非常重要,不是吗?也许您可以将其提取到一个单独的方法中,该方法根据记录的类型(类)获取记录并保持
Store.activate
simpler。如果您甚至需要在
存储
中使用方法
激活
,或者您可以直接在
产品
上调用该方法,请先检查。
describe Product do
  context "after activating" do   # Human readable situation of the test
    let(:product) { Product.new.activate }
    subject { product }           # Make product the subject of the test

    it { should be_active }       # Product should be active
  end
end
describe Store do
  context "when activating a product" do
    let(:product)       { mock }
    let(:store)         { Store.new }

    before do
      product_klass = double                 # Stub the product class, don't mock
      product_klass.stub(:find) { product }  # We want to test product here, not the class
      store.activate(product_klass, 22)
    end

    subject { product }                      # product is the subject again

    it { should_receive(:activate) }         # it should receive the activate message
  end
end