Hi I''m trying to be good and practice full BDD on my current project, and don''t want to abandon it as I have previously (expediency triumphed unfortunately). So expect me to be making frequent ''noob'' style posts... My current issue is with testing assignation across a has_many relationship. I''m aware I shouldn''t be testing the functionality of Rails, but this is behaviour of my code. cart_spec.rb: describe Cart do before(:each) do @product = mock "Trousers" @product.stub!(:class).and_return("Product") @product.stub!(:name).and_return("Brown Trousers") @product.stub!(:price).and_return(23.99) @cart = Cart.new end it "should have 1 item after adding a Product" do @cart.add_product(@product) @cart.items.should have(1).item end it "should have 1 item but with quantity 2 after adding the same product twice" do @cart.add_product(@product) @cart.add_product(@product) @cart.items.should have(1).item end end cart.rb: class Cart < ActiveRecord::Base has_many :items, :class_name => "CartItem", :dependent => :destroy def add_product(product) current = self.items.find_by_name(product.name) if current current.increment_quantity else self.items << CartItem.new_from_product(product) end end end fails with: ''Cart should have 1 item but with quantity 2 after adding the same product twice'' FAILED expected 1 item, got 2 ***** Can anyone explain to me what I''m missing? -- Posted via http://www.ruby-forum.com/.
On 9 Apr 2008, at 06:25, Andy Croll wrote:> current = self.items.find_by_name(product.name)You''re finding by the CartItem name and passing the product name - it looks like that''s drawing a blank match when you''re expecting it not to. How about a cart_item_spec.rb: describe CartItem do it "should be created successfully from a new product" do CartItem.new_from_product(mock_model(Product, :name => "name" )) CartItem.find_by_name("name").should_not be_nil end end I''m guessing this spec will fail with your current code as I don''t think CartItem::new_from_product is working. I''d also probably break down the specs differently and wrap the call to items and make the specs more contained: class Cart def self.find_items_by_name(name) items.find_by_name(name) end end ...and mock this method when testing add_product: it "should add the product when it doesn''t already exist in the cart" do @cart.should_receive(:find_items_by_name).with("name").and return(nil) @cart.add_product(@product) @cart.items.should have(1).item end it "should increment quantity when it does find a product" do @cart.should_receive(:find_items_by_name).with("name").and return(@product) @cart.add_product(@product) @cart.items.should have(1).item end HTH, Chris
Chris Parsons wrote:> I''m guessing this spec will fail with your current code as I don''t > think CartItem::new_from_product is working.Wow. I''ve never had code I hadn''t posted correctly debugged before. You were absolutely right, thanks! Seems my testing approach in that spec was somehow faking out the all the tests I made.> I''d also probably break down the specs differently and wrap the call > to items and make the specs more contained:I think this is where I''m going wrong, I keep letting my tests get too big. Even when I think they are small. Another good rule I''ve picked up from this is to always try and use less than two dots to abstract the models... Big help thanks Chris. -- Posted via http://www.ruby-forum.com/.
Chris Parsons wrote:> it "should increment quantity when it does find a product" do > @cart.should_receive(:find_items_by_name).with("name").and > return(@product) > @cart.add_product(@product) > @cart.items.should have(1).item > endMy final solution for this was to write... describe Cart do before(:each) do @product1 = mock_model Product, :name => "Brown Trousers", :price => 23.99 @product2 = mock_model Product, :name => "Yellow Shirt", :price => 15.74 @cart_item1 = mock_model CartItem, :name => "Brown Trousers", :price => 23.99, :quantity => 1 @cart = Cart.new end it "should increment quantity when it does find a product" do @cart_item1.should_receive(:increment_quantity).once.with(:no_args).and_return(2) @cart.should_receive(:find_item_by_name).twice.with("Brown Trousers").and_return(nil, @cart_item1) @cart.add_product(@product1) @cart.add_product(@product1) @cart.items.should have(1).item end end Which seems to work. I''m right? -- Posted via http://www.ruby-forum.com/.
On 9 Apr 2008, at 12:22, Andy Croll wrote:> it "should increment quantity when it does find a product" do > > @cart_item1 > .should_receive(:increment_quantity).once.with(:no_args).and_return(2) > @cart.should_receive(:find_item_by_name).twice.with("Brown > Trousers").and_return(nil, @cart_item1) > @cart.add_product(@product1) > @cart.add_product(@product1) > @cart.items.should have(1).item > end > end > > Which seems to work. I''m right?Looks good. Don''t forget that with that extra mocking your coverage has dropped on CartItem so you''ll need to ensure that it''s correctly tested also. Cheers Chris