I’m currently struggling a bit trying to keep my controller specs DRY and succinct and down to one assertion per example. I’m running into some difficulties particularly with where to place the actual controller request call within a structure nested to match the various edge cases.
Here’s an example, simplified to demonstrate the problem:
describe MyController do
let(:item) { Factory(:item) }
subject { response }
describe "GET #show" do
before(:each) do
get :show
end
context "published item" do
it { should redirect_to(success_url) }
end
context "unpublished item" do
before(:each) do
item.update_attribute(published: false)
end
it { should redirect_to(error_url) }
end
end
end
Clearly this is a contrived example, but it illustrates what I’d like to do and what’s not working. Mainly, the before block under the “unpublished” context is the problem. What happens is the change I made to the setup data actually happens after the get call due to the way the contexts are nested, so the example in that context is actually working with the initial scenario rather than the one I intend.
I understand why this happens and how contexts nest. I guess what I’d like to have is some way to tell RSpec what I’d like it to run right after any before hooks yet right before any assertions within a given context. This would be perfect for controller specs. I’d like to take advantage of nesting in my controller specs to gradually build up variations of edge cases without having to scatter the get call or even a call to a do_get helper into each of my it assertions. This would especially get annoying to keep in sync with any custom it_should macros I’m using.
Is there anything in RSpec currently to accomplish this? Are there any tricks I can use to get close? It would seem perfectly suited to the way I’ve seen a lot of people writing their controller specs; from what I’ve found, people have basically settled for having do_get helpers called before every assertion. Is there a better way?
The DRY principle states that “Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.” What you’re doing is much more about saving a few characters here and there than keeping things DRY, and the result is a tangled web of dependencies up and down a hierarchy that, as you can see, is a bitch to get to do what you want it to and consequently fragile and brittle.
Let’s start with what you’ve got written out in a way that’s verbose and works:
Now the only “pieces of knowledge” that are being duplicated are the names of the examples, which could be generated by the matchers at the end of each example. This can be resolved in a readable way by using the
examplemethod, which is an alias ofit:There. DRY. And quite readable and easy to change. Now, when you happen to add more examples for either of the contexts, you can add a
let:Now the only duplicated code (not the same as the DRY principle) is the
get. If you really feel strongly about it, you can delegate those calls out to a method likeget_show(id)or some such, but it’s not really buying much at that point. It’s not like the API forgetis going to change from under you, and the only argument togetis theitem‘s id, which you actually care about in the example (so there’s no unnecessary information).As for using
subjectto capture the response and get one-liners out of the deal, that just makes things really difficult to read and doesn’t save you much. In fact, I’ve come to consider usingsubjectin this way to be a smell.Hope this all helps.
Cheers,
David