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
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
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
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…
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
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