Vaguely about how to use layout and stubs

I have a class and specification.

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 

Running the specification does not work . I get:

 Mock received unexpected message :find_by_id with (1) 

To satisfy this, I add product.should_receive(:find_by_id).with(1).and_return(product) before the line store.activate(product, 22) . (This seems wrong because I do not want my test to know too much about the internal methods that I am testing)

Running the spec again , I get a failure when the following line returns false instead of the expected true :

 product.should be_active 

So, it returns false , because product.update_attribute :active, true did not actually set active to true : it was just swallowed by the layout.

I have so many questions. How to go about rspec'cing? How should I check this? Am I using mocks and stub correctly?

Any help is greatly appreciated.

+4
source share
2 answers

I think activation logic is not part of the Store . If this were declared in Product , the test would look much more natural to me:

 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 

From there, you can re-write your Store method as follows:

 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 
+2
source

I agree with @padde that product activation should be in the Product model, as:

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

However, I would reorganize the test to bring it into line with the standard Rspec standards:

 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 

And Store test:

 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 

I removed the wait on product_klass , as this is not quite what you need for testing in this case. You may like this as a standalone test.

Using let , subject and context allows you to standardize your tests and allows rspec to do some neat things, such as creating people-friendly documentation of your classes. For more information on rspec best practices, see betterspecs .

+2
source

Source: https://habr.com/ru/post/1482073/


All Articles