views:

113

answers:

2

n the .net world, my specs would follow the Arrange, Act, Assert pattern. I'm having trouble replicating that in rspec, because there doesn't appear to be an ability to selectively verify your mocks after the SUT has taken it's action. That, coupled with the fact that EVERY expectation is evaluated at the end of each 'It' block, is causing me to repeat myself in a lot of my specs.

Here's an example of what I'm talking about:

describe 'AmazonImporter' do
    before(:each) do
        Kernel.**stubs**(:sleep).with(1)
    end
    # iterates through the amazon categories, and for each one, loads ideas with 
    # the right response group, upserting ideas as it goes
    # then goes through, and fleshes out all of the ideas that only have asins.
    describe "find_new_ideas" do
        before(:all) do
            @xml = File.open(File.expand_path('../amazon_ideas_in_category.xml', __FILE__), 'r') {|f| f.read }
        end

        before(:each) do
            @category = AmazonCategory.new(:name => "name", :amazon_id => 1036682)
            @response = Amazon::Ecs::Response.new(@xml)
            @response_group = "MostGifted"
            @asin = 'B002EL2WQI'
            @request_hash = {:operation => "BrowseNodeLookup", :browse_node_id => @category.amazon_id,
                                                    :response_group => @response_group}
            Amazon::Ecs.**expects**(:send_request).with(has_entries(@request_hash)).returns(@response)
            GiftIdea.expects(:first).with(has_entries({:site_key => @asin})).returns(nil)
            GiftIdea.any_instance.expects(:save)            
        end

        it "sleeps for 1 second after each amazon request" do
            Kernel.**expects**(:sleep).with(1)
            AmazonImporter.new.find_new_ideas(@category, @response_group)
        end

        it "loads the ideas for the given response group from amazon" do
            Amazon::Ecs.**expects**(:send_request).
                with(has_entries(@request_hash)).
                returns(@response)

            **AmazonImporter.new.find_new_ideas(@category, @response_group)**
        end

        it "tries to load those ideas from repository" do
            GiftIdea.expects(:first).with(has_entries({:site_key => @asin}))
            **AmazonImporter.new.find_new_ideas(@category, @response_group)**
        end

In this partial example, I'm testing the find_new_ideas method. But I have to call it for each spec (the full spec has 9 assertion blocks). I further have to duplicate the mock setup so that it's stubbed in the before block, but individually expected in the it/assertion block. I'm duplicating or nearly duplicating a ton of code here. I think it's even worse than the highlighting indicates, because a lot of those globals are only defined separately so that they can be consumed by an 'expects' test later on. Is there a better way I'm not seeing yet?

(SUT = System Under Test. Not sure if that's what everyone calls it, or just alt.net folks)

A: 

you can separate them using "context" if that helps...

http://wiki.github.com/dchelimsky/rspec/faq

rogerdpack
+1  A: 

You can use shared example groups to reduce duplication:

shared_examples_for "any pizza" do
  it "tastes really good" do
    @pizza.should taste_really_good
  end
  it "is available by the slice" do
    @pizza.should be_available_by_the_slice
  end
end

describe "New York style thin crust pizza" do
  before(:each) do
    @pizza = Pizza.new(:region => 'New York' , :style => 'thin crust' )
  end

  it_behaves_like "any pizza"

  it "has a really great sauce" do
    @pizza.should have_a_really_great_sauce
  end
end

Another technique is to use macros, which is handy if you need similar specs in different classes.

Note: the example above is borrowed from The RSpec Book, Chapter 12.

zetetic