DRY Controller Specifications with RSpec

I'm currently struggling a bit, trying to keep the DRY controller specifications both concise and down to one statement for example. I encounter some difficulties, especially where to place the controller request call in a structure nested according to various edge cases.

Here is 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 would like to do and what does not work. Mostly, the before problem in the context of "unpublished." What happens is that the change I made for the configuration data really happens after the get call because the contexts are nested, so the example in this context actually works with the original script, and not with the one I intend to.

I understand why this happens and how contexts nest. I suppose I would like to have some way of telling RSpec that I would like it to start right after any before tags, but right before any statements in this context. This would be ideal for the specification of the controller. I would like to use the ability to embed variations of edge cases in my controller specifications without having to scatter the get call or even the do_get helper do_get into each of my it statements. This would be especially annoying to sync with any it_should custom macros that I use.

Is there anything in RSpec to accomplish this? Are there any tricks I can use to get closer? This would seem to be great for the way I saw a lot of people writing their controller specifications; from what I found, people basically decided that do_get helpers were called before each statement. Is there a better way?

+6
source share
2 answers

The DRY principle states that "each piece of knowledge must have one, unambiguous, authoritative representation in the system." What you do is much more about saving a few characters here and there than keeping things DRY, and the result is a tangled network of dependencies up and down the hierarchy, which, as you can see, is a bitch to do what you want it and therefore fragile and fragile.

To begin with, you wrote out in such a way that verbose and works:

 describe MyController do describe "GET #show" do context "published item" do it "redirects to the success url" do item = Factory(:item, published: true) get :show, :id => item.id response.should redirect_to success_url end end context "unpublished item" do it "redirects to the error url" do item = Factory(:item, published: false) get :show, :id => item.id response.should redirect_to error_url end end end end 

Now, the only "pieces of knowledge" that are duplicated are the names of examples that can be generated by helpers at the end of each example. This can be resolved in a readable way using the example method, which is an alias of it :

 describe MyController do describe "GET #show" do context "published item" do example do item = Factory(:item, published: true) get :show, :id => item.id response.should redirect_to success_url end end context "unpublished item" do example do item = Factory(:item, published: false) get :show, :id => item.id response.should redirect_to error_url end end end end 

There. DRY. And quite readable and easy to change. Now that you add more examples for any of the contexts, you can add let :

 describe MyController do describe "GET #show" do context "published item" do let(:item) { Factory(:item, published: true) } example do get :show, :id => item.id response.should redirect_to success_url end example do # other example end end # ... end end 

Now the only duplicated code (not the same as the DRY principle) is get . If you are really determined to do this, you can delegate these calls to a method, such as get_show(id) or some such method, but in reality it doesn’t buy a lot. It's not like the API for get will change from under you, and the only argument to get is the item identifier, which you really like in this example (so there is no unnecessary information).

As for using subject to capture the response and exit one transaction from the same line, it just makes things very difficult to read and does not save you. In fact, I decided to use subject in this way as a smell .

Hope this helps.

Cheers, David

+6
source

Will

 context "unpublished item" do let(:item) do Factory(:item, published: false) end it { should redirect_to(error_url) } end 

works for you? BTW, before the default is before(:each) so that you can DRY, you specify a little more.

UPDATE: you can also isolate examples with anonymous contexts, for example:

 describe "GET #show" do let(:show!) do get :show end context do before { show! } context "published item" do it { should redirect_to(success_url) } end # another examples with show-before-each end context "unpublished item" do before do item.update_attribute(published: false) show! end it { should redirect_to(error_url) } end end 
+3
source

Source: https://habr.com/ru/post/908368/


All Articles