#71
Sep 17
Testing Controllers with RSpec
Controllers are tricky to test, and there's no perfect way to do it. In this episode you will see how I test controllers, and my reasoning behind it.
Resources
describe MenuItemsController, "creating a new menu item" do integrate_views fixtures :menu_items it "should redirect to index with a notice on successful save" do MenuItem.any_instance.stubs(:valid?).returns(true) post 'create' assigns[:menu_item].should_not be_new_record flash[:notice].should_not be_nil response.should redirect_to(menu_items_path) end it "should re-render new template on failed save" do MenuItem.any_instance.stubs(:valid?).returns(false) post 'create' assigns[:menu_item].should be_new_record flash[:notice].should be_nil response.should render_template('new') end it "should pass params to menu item" do post 'create', :menu_item => { :name => 'Plain' } assigns[:menu_item].name.should == 'Plain' end end




Great Episode Ryan, glad to get my Railscasts fix :)
Very nice screencast!
I like to test almost same way. I don't even use stub at that place. I find, that using params is better. For example I don't need to test if params get to the model. If not, then there can't be save and test fails.
@Inimene, the reason I prefer to stub out the validation is to remove a dependency on the model. I don't want the controller test to break when I change the model validation.
Nice one Ryan :)
One thing I found surprising is that you don't use Growl notifications for your autotesting. I certainly cannot live without my red and green smileys.
Instead of
post 'create'
assigns[:menu_item].should be_new_record
you can also do:
lambda { post 'create' }.should change { MenuItem.count }.by(1)
and similarly
lambda { post 'create' }.should_not change { MenuItem.count }
@pimpmaster, I normally use growl, but I just don't for the screencast because it is distracting.
Hey Ryan;
Check out Lachie Cox's Hornsby Scenarios.
http://blog.allen.com.au/2007/9/17/33-702635-151-099434-hornsby
and
http://blog.smartbomb.com.au/2007/8/29/hornsby
It's awesome for getting rid of fixtures.
@Matt, thanks for the link. That seems like a good solution in the model tests where I need some initial data to test upon. However, in the controller layer I'm sidestepping the problems with fixtures.
What I'm doing is making the tests independent of the data inside the fixtures. The only reason I'm loading them is to get loops and such in the view to execute so it finds any errors in the code. The controller tests don't care what it contains.
As mentioned in the screencast, the only thing I depend upon in the fixtures is that:
1. there's a record with the ID of 1 so I can reference it if need be
2. the data inside the fixtures is valid (so it doesn't raise any nil object errors)
If I need to test anything that depends on the content of the fixtures I'll move this to a model test and use something like what you linked to instead of fixtures.
What about to do when you're are using an authentication tool in your program, how to you tell your tests/specs that the should log in (authenticated)first?
commenting the line with the filter when running the tests in the application controller will not be the best solution, so ...? any good ideas?
thx in advance.
@QuBiT, usually I just stub out the "current_user" method in the controller to return a user of my choice. That way I can "login" any user I want.
Hey I've been using this technique for rspec to check authentication, it uses fixtures so I'm not sure if it is best practice but its pretty easy to set up.
http://pastie.caboo.se/98834
Please let me know if there is a better or easier way.
I've wondered how other people deal with this.
Cheers,
Josh
Edit: moved code into pastie --Ryan
@Joshua, looks like a good solution. I've done something similar in the past. It is relying on the fixture data, but as long as it's not relying on the relationship of other fixture records then I think it's perfectly acceptable.
off topic : wish for screencast topic.
maybe you could show in one of the following screencasts:
helper_method :whatever
versus
helper :whatever include WhateverHelper
... this was quite confusing for me in the beginning and i still have always to think instead of doing the appropriate one automatically
Why do you prefer mocha? What problems do you have with stubbing and mocking in Rspec itself.
@Frank, I've had issues with stubbing class methods. Sometimes the stub didn't get reset between tests which caused very difficult to find problems. So far I haven't had any problems with mocha and it feels much more solid to me.
In this episode you mentioned that it is a best practice to only do one assert per test. Why is that? I find that when testing a method I have several things I want to check to make sure they all work together.
@Mark, I picked up the practice after reading a Jay Fields article. He explains it better than I can:
http://blog.jayfields.com/2007/06/testing-one-assertion-per-test.html
Ryan, Thanks for the great screencasts they have helped me out tremendously. I really like your simplified tests. I'm new to the testing game (its never too late right?!) Any chance of seeing how you'd handle testing the other restful actions?
Ryan, thank you very much for your work! It is really enlightening! I actually learned from railscasts more that from awdwr book! Thank you!
By the way, I would like to ask you how you get that nice color output in autotest?
you should really put rspec in it's name, as i searched for it quite a while before actually diving in to see what it was about
@Sean, for fully RESTful controllers I usually start with very minimal tests. It's actually more minimal than what I'm showing here. Here's an example:
http://pastie.caboo.se/159422
For the most part I'm just testing the response. Even though this willl likely result in 100% test coverage with rcov, it's not really. For example I'm not actually testing the update of the attributes in the update action. I feel it's okay to assume this kind of behavior for a standard RESTful controller. This allows me to focus more on the exceptions to the normal REST than the expected behavior.
For example, if you have some kind of authorization then it's fairly easy to add some tests for this. But if the starting specs are too complex then this exceptional behavior can easily become lost in the specs.
@Damian, the colors are from rspec. But if you want them with test/unit then I think there's a gem called redgreen which can do this.
@oin, fixed. Thanks for the suggestion.
In general I try to mirror all simple rails validations (presence, length, uniqueness etc...) with indexes and not-null fields in the database. In your screencast you stub out valid? to bypass validations, but if there were any not-null fields in your table that spec would fail.
If you added a not-null constraint to your database at a later date, you'd have to go to that spec and update the post methods to include params that correspond with the not-null fields, which can quickly become unmaintainable since your controller specs are now tied to your model's fields.
I think in your example stubbing save would have been the better way to go. I typically mock the entire class, but it's a lot of work when you use integrate_views. Stubbing save would also make the should_be new_record call unnecessary.
Thanks for the great screencasts - I've learned a lot from you.f
how would you write rspec code if objects are defined from their associations like
@task = @projects.tasks.build( params[ :task ]
@tasks = @project.tasks
etc
Why do you prefer mocha over rspec's mocking scheme?