DRY the SUT up - RSpec and Mocking question
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)
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.
you can separate them using "context" if that helps...
https://github.com/dchelimsky/rspec/wiki/faq
精彩评论