Thursday, May 8, 2008

Rails: When Functional Tests are not Functional !!

One of the most well-known virtues of the Rails framework is that everything has its own place to go in the project hierarchy. A perfect example is testing. Everything that is associated with tests goes under the "test" directory. Moreover, further grouping take place under the "test" directory. Unit tests are to be used when testing models functionality, and functional tests are to be used when testing controllers functionality. The good hidden feature in this modular division scheme is that, the shape of the functional test cases you write to cover your controllers code actually measures the degree of your code quality.

A typically 'good' rails functional test case could look like this:

def test_create_new_item
post :create, { :name => "name",
:description => "description" },
{ :session_key1 => "session_value1" }
assert_response :success
assert_template "create"
end

while a 'bad' rails functional test case could look like this:

def test_create_new_item
post :create, { :name => "name",
:description => "description" },
{ :session_key1 => "session_value1" }
assert_not_nil Item.find_by_name("name")
assert_equal users(:user1).item_count, 10
assert_response :success
assert_template "create"
end

So, what exactly makes the first example better than the second? Basically, functional tests is just about simulating requests and testing the 'behavior' of the controller action in such requests. The word 'behavior' means the group of characteristics of the request and response. For example, A good functional test case is that simulates a request, then just asserts for the request and/or response headers and/or body, like the first example. A bad example is asserting for a specific state of the data after the request is handled, like the second example. This is not right because, in such a case, you are making one of two mistakes:

-You are adding some redundant assertions to test some model functionality that is already covered in unit tests.
-Your controllers incorporate some model-level chunks of code. Consequently, you needed to cover them in the functional tests. This is a strong indicator that these chunks of code are misplaced in the controllers. They sure need to be abstracted to some model functionality, being covered in unit tests.

So the bottom line is, in your functional tests, mind only the 'behavior' of the controller action you're testing. If you find your functional test cases not covering all controller code after all, try to reconsider your controller code. Go for some model abstraction of your controller code, and cover the new model code in the unit tests.


No comments: