#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 33 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. 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.


27. Greg H Sep 02, 2008 at 13:56

Q1 - Overall Ryan/guys would you recommend RSpec for controller & model testing over Rails tests? (just want to jump in with one of them)

Q2 - Can I assume that the core testing framework (e.g. rails unit testing versus RSpec) is a kind of separate issue to whether you using mocking or not? i.e. you could use Mocha with either the rails unit test framework or Rspec? Or is there something in Rspec that integrates moreso with mocking?


28. Neil Sep 23, 2008 at 04:01

More testing (rspec) screencasts please. I get this guilty feeling even mentioning the word.

The community encourages testing, but then just refers me to a peepcode(which are great) or some chapter in a book and carries on.

If testing is so important then why are so few screencasts here on testing? We need someone like yourself to 'glamourize' testing. If every second screencast were about testing I'd feel compelled to think about testing in my everyday coding.


29. Thomas Oct 31, 2008 at 12:24

Ryan,
you said you had problems stubbing class methods. I have similar issues. Other tests fail because stubbing seems to "carry over".

If I run a single rspec, it passes, but rake rspec fails, which makes me think something isn't being reset.

Do you have any suggestion what to do instead?

For example, in my controller, I do
Product.find params[:id]
it's tempting to do the following in a rspec
Product.stub!(:find).and_return(some_product)


30. Justin Nov 15, 2008 at 19:56

Thanks for the great screencast.

While I think the way you have structured this test is very interesting in that it covers many possible implementations for saving an object to the DB using ActiveRecord; I think there is a pitfall to this method in that you are relying/depending on the underlying platform's implementation, which you as a developer have no control over.

While there may be a small chance of the Rails implementation changing any time soon, you still have no control over this (unless you are one of the Rails developers of course :) ).

Also, I feel that writing tests in the way that has been demonstrated here, while very clever, is simply more difficult, both to write and to read. It seems like you could easily spend more time trying to figure out clever ways to write tests with very high coverage "areas", which would take away from the task of getting your app tested and implemented the specific way you want.

Also, as for readability, this test requires a deeper knowledge of the Rails framework to understand. When someone new comes in to look at the test and underlying implementation, they will see the use of the "valid?" function in the test, but yet will not find this method called anywhere in the implementation. If this person is not already familiar with how ActiveRecord is implemented, then they may have to start going through Rails docs just to make sense of the test code.

While this method for high test coverage is certainly clever, and I thought it was a great screencast, I also feel it has some serious drawbacks.


31. John Schank Dec 29, 2008 at 14:34

Hi there,
Love the podcasts...
Way back a year ago (items 10 and 11 above) you mention stubbing out the current_user.

Could you please elaborate on that.

What do I do if I have an rspec generated spec (from the scaffold generator) for a controller, which works fine (all the tests pass) but now I want to make the entire controller require a login. (using a before filter)

[Note: I'm using restful authentication, as provided in bort. See http://github.com/fudgestudios/bort/tree/master]

Do I need to re-write all of the tests that pass (but assume no login is required) or is there an easy way to make the whole set of tests act as though a user is logged in.

Then, I assume I just need to write a test or two which verifies correct behavior when a user is NOT logged in. Which probably just confirms a redirect.

Thanks,
John Schank


32. Shreyans Feb 13, 2009 at 09:50

Hi Ryan,
It would be too nice of you if you could include an episode for setting up RSpec for writing rspec tests. I tried to follow your video but was not able to set up rspec in the first place.. Help!


33. Teresa Mar 06, 2009 at 17:45

I'm new to Rails and trying to write functional tests, a simple test I thought. I have regular users and read-only users, and I wanted to verify that links to add/edit/delete are only displayed for the regular users. Thought I could just do an assert_select to see that an 'a' tag exists with id like 'addLink'. But for some reason the test always fails because it isn't finding the 'a' tag. I cannot find any good references on doing this. Can I just assert_select 'a', 'addLink', or must I somehow traverse through other parts of the view's form? Would appreciate any help, even if it's just pointing me to a location that provides extensive examples of testing controllers and the views they render.


34. Sam Woodard Oct 12, 2009 at 15:13

I am having trouble using stubs method. The method is being stubbed across tests and is breaking some of my tests. Any suggestions?


35. Joseph Nov 27, 2009 at 08:20

Great screencast! But stubbing :valid? doesn't help when the underlying database table has the :null => false constraint on any of the columns. The database will complain when the controller tries to save the record. Any suggestions?


36. ugg boots online Jan 24, 2010 at 22:25

It would be too nice of you if you could include an episode for setting up RSpec for writing rspec tests. I tried to follow your video but was not able to set up rspec in the first place.. Help!


37. Louis Vuitton Monogram Denim Neo Speedy M95019 Jul 16, 2010 at 19:22

I was actually looking for this resource a few weeks back. Thanks for sharing with us your wisdom.This will absolutely going to help me in my projects .


38. asics shose Jul 28, 2010 at 00:14

Hi Ryan,
It would be too nice of you if you could include an episode for setting up RSpec for writing rspec tests. I tried to follow your video but was not able to set up rspec in the first place.. Help!


39. free directory list Aug 11, 2010 at 22:21

I think this is very dangerous


40. emrah eren Aug 13, 2010 at 19:13

hello thank you good information


41. ARGOSS Aug 13, 2010 at 23:20

thanks


42. Jordan basketball shoes Aug 23, 2010 at 22:52

Good job! very good cord.This is all very new to me and this article really opened my eyes. thank you


43. louis vuitton shoes Aug 26, 2010 at 23:20

Thanks for sharing your article. I really enjoyed it. I put a link to my site to here so other people can read it. My readers have about the same interets


44. snow boots Aug 31, 2010 at 00:56

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.


45. tagheuer Aug 31, 2010 at 17:47

I have always liked Outdoor movies, a child standing at the window, looked out from home

to the following. Will be able to see the staff busy figure, a huge white cloth has a

child hang up and soon will be able to see the movie.


46. louis vuitton sunglasses Sep 01, 2010 at 22:40

Hi, good post, and awesome weblog you have here!
What’s this theme you use? Is it customized?
If customized can you please facilitate me the website of the company who designed it for you?
I would apreciate to have him/her do one for me also!

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
Give Back to Open Source