Sunday, September 09, 2007
Rails: Testing Controllers
Yesterday, Mike Clark posted a blog entry asking "How Would You Test This?" The topic of the entry is how to test controllers. Mike's article is good, and you should start there for context.
I think Mike has the Functional tests well covered in his entry. However, I've never been able to accept that Controllers can only be functionally tested. In my earlier Rails days I would probably have written something similar to the example below and considered it a Unit Test. (Sorry Mike, I prefer Mocha to Flexmock)
There's a few concerns with the above example. Probably the largest concern is that a lot of mocking almost always results in brittle tests. Another concern is that there's so much noise in the test that it's hard to determine what the intent of the test is. Furthermore, the test is clearly specifying not what should be done, but how it should be done.
On my current project, David Vollbracht created something called an IsolatedControllerTest. The IsolatedControllerTest class mocks the render method and does a few other things to give you the ability to test your controllers in isolation. While David had the right idea, it simply didn't catch on. We have a few IsolatedControllerTests; however, the majority of controller tests are all Functional.
As Mike points out, the current situation often leads to a lack of testing controllers. I'm no more okay with that option than Mike is, but we've had other more interesting problems to solve so I put the thought on a back burner. Luckily, Mike isn't willing to settle.
I think part of the problem is that controllers are not Good Citizens. Controllers violate the first rule of Good Citizenship. Upon creation (Controller.new), controllers are not in a valid state. Instead, controllers depend on being initialized within the framework and having their state set post construction time. That ends up being a problem for unit testing since it requires each test to set additional state on newly created controllers.
Another problem is that methods are marked as protected. For example the *_url and *_path methods are all only accessible from within a controller. I'm sure this was done with the best intentions; however, I subscribe to the philosophy that making something public to increase testability is a good idea.
One of the larger shortcomings that controllers have is that they don't behave like POROs (Plain Old Ruby Objects). Ideally, testing controllers should be as easy as writing the following tests.
I look forward to the day when I can create controllers (via Controller.new) and I will have a valid object that I can easily test (with tests similar to the ones above).
I think Mike has the Functional tests well covered in his entry. However, I've never been able to accept that Controllers can only be functionally tested. In my earlier Rails days I would probably have written something similar to the example below and considered it a Unit Test. (Sorry Mike, I prefer Mocha to Flexmock)
endThere's a few concerns with the above example. Probably the largest concern is that a lot of mocking almost always results in brittle tests. Another concern is that there's so much noise in the test that it's hard to determine what the intent of the test is. Furthermore, the test is clearly specifying not what should be done, but how it should be done.
On my current project, David Vollbracht created something called an IsolatedControllerTest. The IsolatedControllerTest class mocks the render method and does a few other things to give you the ability to test your controllers in isolation. While David had the right idea, it simply didn't catch on. We have a few IsolatedControllerTests; however, the majority of controller tests are all Functional.
As Mike points out, the current situation often leads to a lack of testing controllers. I'm no more okay with that option than Mike is, but we've had other more interesting problems to solve so I put the thought on a back burner. Luckily, Mike isn't willing to settle.
I think part of the problem is that controllers are not Good Citizens. Controllers violate the first rule of Good Citizenship. Upon creation (Controller.new), controllers are not in a valid state. Instead, controllers depend on being initialized within the framework and having their state set post construction time. That ends up being a problem for unit testing since it requires each test to set additional state on newly created controllers.
Another problem is that methods are marked as protected. For example the *_url and *_path methods are all only accessible from within a controller. I'm sure this was done with the best intentions; however, I subscribe to the philosophy that making something public to increase testability is a good idea.
One of the larger shortcomings that controllers have is that they don't behave like POROs (Plain Old Ruby Objects). Ideally, testing controllers should be as easy as writing the following tests.
endI look forward to the day when I can create controllers (via Controller.new) and I will have a valid object that I can easily test (with tests similar to the ones above).
Labels: controller, rails, testing
Thursday, March 08, 2007
Rails: Clean up Controller Tests
Traditional Rails Controller tests begin with code very similar to the following code.
require File.dirname(__FILE__) + '/../test_helper'If you use generators you may not notice or care about all this boiler plate code; however, if you generally create your files from scratch you may be interested in the following method that I add to Object (in my test_helper.rb).
# Re-raise errors caught by the controller.
class SomeController; def rescue_action(e) raise e end; end
class SomeControllerTest < Test::Unit::TestCase
def setup
@controller = SomeController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
end
class ObjectThe above code does basically the same thing, but it allows me to define my controller tests in a much cleaner way.
def controller_tests(&block)
full_path_file_name = eval "__FILE__", block.binding
test_name = File.basename(full_path_file_name, ".rb")
controller_name = test_name.chomp("_test")
require controller_name
controller = controller_name.camelize.constantize
controller.class_eval do
def rescue_action(e)
raise e
end
end
test_class = eval "module Functionals; class #{test_name.camelize} < Test::Unit::TestCase; self; end; end"
test_class.class_eval do
define_method :setup do
@controller = controller.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
end
end
test_class.class_eval &block
end
end
require File.dirname(__FILE__) + '/../test_helper'This change also provides the benefit of automatic test class renames when the test file name changes.
controller_tests do
test "should display order number index" do
get :index
...
end
test "should create new order on new" do
get :new
...
end
end
Labels: controller, rails, ruby, test


