It’s pretty common to have multiple tests that are nearly the same.

In one app, we support bidding on both online and hammer auctions (auctions with people physically present). They’re separate controllers but with a lot of shared code and behavior.

We want to test both, but we’d rather not write two almost identical tests if we can help it.

So we’ve been using RSpec shared examples, with the ​​template method pattern​​to account for the differences, and we like it.

Here’s a simplified example:

spec/request/online_bidding_spec.rb



1 2 3 4 5 6 7 8 9 10 11 12



require "spec_helper"
require "support/shared_examples/bidding"

describe "Bidding online" do
include_examples :bidding

let(:auction) { FactoryGirl.create(:online_auction) }

def auction_path
online_auction_path(auction)
end
end



spec/request/hammer_bidding_spec.rb



1 2 3 4 5 6 7 8 9 10 11 12



require "spec_helper"
require "support/shared_examples/bidding"

describe "Bidding at hammer auction" do
include_examples :bidding

let(:auction) { FactoryGirl.create(:hammer_auction) }

def auction_path
hammer_auction_path(auction)
end
end



spec/support/shared_examples/bidding.rb



1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31



shared_examples :bidding do
it "lets you bid when logged in" do
log_in # Implemented somewhere else.
visit auction_path
place_bid
bid_should_be_accepted
end

it "doesn't let you bid when not logged in" do
visit item_path
place_bid
bid_should_be_rejected
end

def auction_path
raise "Implement me."
end

def place_bid
fill_in "Bid", with: 123
click_button "Place bid"
end

def bid_should_be_accepted
page.should have_content("OK! :)")
end

def bid_should_be_rejected
page.should have_content("NO! :(")
end
end


The only template method here is ​​auction_path​​ . The shared example makes sure, by raising, that it’s overridden in your concrete specs.