I have a bunch of Rails 3.1 controllers which all have very similar testing requirements. I have extracted out the common code (all Test::Unit style), e.g. the following three tests are completely reusable across all of them:
def create
new_record = { field_to_update => new_value }
create_params = { :commit => "Create", :record => new_record }
post :create, create_params
end
test "should_not_create_without_login" do
assert_no_difference(count_code) do create; end
assert_need_to_log_in
end
test "should_not_create_without_admin_login" do
login_as_non_admin
assert_no_difference(count_code) do create; end
assert_needs_admin_login
end
test "should_create" do
login_as_admin
assert_difference(count_code) do create; end
assert_redirected_to list_path
end
and I intended that it could go in an abstract class which inherits from ActionController::TestCase
. Then each functional test would only need to override the abstract methods, ending up pleasingly small and clean, e.g.
class Admin::AvailabilitiesControllerTest < Admin::StandardControllerTest
tests Admin::AvailabilitiesController
def model ; Availability end
def id_to_change ; availabilities(:maybe).id end
def field_to_update; :value end
def new_value ; 'maybe2' end
def list_path ; admin_availabilities_path end
end
However, when I try this, it appears that the framework tries to run the test methods directly from the abstract class, rather than from the inherited class:
E
===================================================================================================
Error:
test_should_not_create_without_login(Admin::ControllerTestBase):
NoMethodError: undefined method `model' for test_should_not_create_without_login(Admin::ControllerTestBase):Admin::ControllerTestBase
test/lib/admin_controller_test_base.rb:7:in `count_code'
test/lib/admin_controller_test_base.rb:68:in `block in <class:ControllerTestBase>'
===================================================================================================
I've heard that other testing frameworks and gems can provide mechanisms for meta-programming of tests, so maybe I'm going about this in entirely the wrong way. But I've tried several things and looked at RSpec, coulda, shoulda, context, contest ... and I still can't see a way to achieve what I'm after. Any ideas? Thanks!
I finally figured this out - once I realised that this is a general Ruby Test::Unit issue rather a Rails testing issue, a quick google instantly revealed How do I inherit abstract unit tests in Ruby? which already had a good answer. Then the only missing piece was being able to use the syntactic sugar:
rather than
I found the answer to this within the ActiveSupport codebase itself, under
lib/active_support/test_case.rb
:This module defines
test
as a class method (which is whyextend
is required rather thaninclude
).So the complete solution looks like this:
The downside of this module-based approach is that you can't override the included tests. I guess that's just a fundamental limitation of Test::Unit - maybe the best answer is to move to RSpec, but I don't know it well enough yet to be sure.