对如何使用mock和stubs感到困惑

时间:2013-05-22 03:30:21

标签: 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 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)

为了满足这一要求,我在行product.should_receive(:find_by_id).with(1).and_return(product)之前添加了store.activate(product, 22)。 (这似乎是错误的,因为我不希望我的测试对我正在测试的方法的内部知识太多了)

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

product.should be_active

所以,它正在返回false,因为product.update_attribute :active, true并未真正将active设置为true:它只是被模拟所吸收。

我有很多问题。如何进行rspec'cing?我应该如何测试这个呢?我是否正确使用了模拟和存根?

非常感谢任何帮助。

2 个答案:

答案 0 :(得分:2)

我认为激活逻辑根本不属于Store。如果它在Product中声明,对我来说测试看起来会更自然:

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

从那里你可以重写这样的Store方法:

class Store
  def activate(product_klass, product_id)
    product = product_klass.find(product_id)
    product.activate
  end
end

describe Store do
  it "should activate an inactive product" do
    product = mock
    product.should_receive(:activate)
    product_klass = mock
    product_klass.should_receive(:find).with(22).and_return(product)
    store = Store.new
    store.activate(product_klass, 22)
  end
end

答案 1 :(得分:2)

我同意@padde关于产品激活应该在Product模型上的事实,因为:

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

但是,我会重构测试,使其与Rspec标准实践一致:

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

Store测试:

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

我删除了对product_klass的期望,因为在这种情况下,这并不是您真正想要测试的内容。您可能希望将其作为单独的测试。

使用letsubjectcontext以标准方式安排测试,并允许rspec做一些巧妙的事情,例如生成类的人性化文档。有关rspec最佳实践的更多信息,请查看betterspecs