Using Rspec's shared_examples to spec new behavior
Suppose we have a CartController and a corresponding spec. At the moment, only logged-in users can add items to their carts, and we want to support carts for guest users.
So far, the spec might look like this:
describe CartController do
let(:user) { create(:user) }
let(:item) { create(:item) }
let(:response_items) { JSON.parse(response["items"]) }
describe :index do
it "returns items in the cart" do
user.cart.add(item)
get :index
response_items[0]["id"].should == item.id
end
end
describe :create do
it "adds an item to the cart" do
post :create, id: item.id
response_items[0]["id"].should == item.id
end
end
describe :destroy do
it "removes an item from the cart" do
user.cart.add(item)
delete :destroy, id: item.id
response_items.should be_empty
end
end
end
Now, to add support for guest carts, it might be tempting to add stuff like:
it "adds an item to the cart when user is a guest"
it "removes an item to the cart when user is a guest"
But that will end up repeating a lot of the previous specs. It's important to isolate what's really changing, which is the user. So, let's set up some context blocks for the two scenarios:
context "signed in user" do
let(:user) { create(:user) }
end
context "guest user" do
let(:user) { User.new }
end
Now, we just need our previous specs to run in these two contexts. We can do that by moving them into shared examples and running those examples in our contexts:
shared_examples "cart requests" do
describe :index
describe :create
describe :destroy
end
context "signed in user" do
let(:user) { create(:user) }
it_behaves_like "cart requests"
end
context "guest user" do
let(:user) { User.new }
it_behaves_like "cart requests"
end
With this approach, we've added a whole new layer of behavior to our specs with very little repetition or actual code.
And most importantly, since we've reused the previous specs, we can be certain that all of the functionality a real user has is also available to guest users.