#71
Sep 17, 2007

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.
Download (17.6 MB, 11:17)
alternative download for iPod & Apple TV (14.6 MB, 11:17)

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

RSS Feed for Episode Comments 27 comments

1. AdulteratedJedi Sep 17, 2007 at 01:04

Great Episode Ryan, glad to get my Railscasts fix :)


2. Sebastien Hugues/ Sep 17, 2007 at 04:42

Very nice screencast!


3. Inimene Sep 17, 2007 at 06:03

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.


4. Ryan Bates Sep 17, 2007 at 09:05

@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.


5. pimpmaster Sep 17, 2007 at 11:28

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.


6. Carl Porth Sep 17, 2007 at 12:07

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 }


7. Ryan Bates Sep 18, 2007 at 08:17

@pimpmaster, I normally use growl, but I just don't for the screencast because it is distracting.


8. Matt Allen Sep 18, 2007 at 14:35

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.


9. Ryan Bates Sep 18, 2007 at 16:44

@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.


10. QuBiT Sep 19, 2007 at 08:42

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.


11. Ryan Bates Sep 19, 2007 at 11:52

@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.


12. Joshua Garner Sep 19, 2007 at 15:18

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


13. Ryan Bates Sep 19, 2007 at 16:23

@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.


14. nicolash Sep 20, 2007 at 04:22

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


15. Frank Sep 20, 2007 at 07:54

Why do you prefer mocha? What problems do you have with stubbing and mocking in Rspec itself.


16. Ryan Bates Sep 20, 2007 at 10:37

@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.


17. Mark Oct 04, 2007 at 09:11

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.


18. Ryan Bates Oct 04, 2007 at 11:30

@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


19. Sean Jan 07, 2008 at 19:24

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?


20. Damian Feb 15, 2008 at 01:27

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?


21. oin Feb 29, 2008 at 05:26

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


22. Ryan Bates Feb 29, 2008 at 10:39

@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.


23. Jeff Dean Apr 05, 2008 at 00:57

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


24. pamthree Apr 14, 2008 at 01:12

how would you write rspec code if objects are defined from their associations like

@task = @projects.tasks.build( params[ :task ]

@tasks = @project.tasks

etc


25. Dandre Gregory May 09, 2008 at 11:17

Why do you prefer mocha over rspec's mocking scheme?


26. kino May 23, 2008 at 01:56

The manifold (and it must not be supposed that this is true) is the clue to the discovery of our hypothetical judgements; therefore, the Transcendental Deduction is the key to understanding space.


27. Treetoad Aug 26, 2008 at 06:20

or somewhat more directly said, kino:

(paraphrasing the Prolegomena-Kant's more 'accessible' work-here) Space and time are the necessary pre-conditions of all future metaphysics.

God, I love being pretentious! (AND pedantic!)

Right, gross simplifications in response to obtuse philosophical musings over, just wanted to offer a belated thanks to you Ryan--this 'cast has just redeemed Rspec for me.

Add your comment:

(SKIP THIS ONE)

(required)

(not shown)


(use pastie or gist for code)

sponsored by:
if you want to help:
required:
Get Quicktime Player