Saturday, April 26, 2008

TestCase HeadCase

I'm writing a reeeeeally simple rails application, but want to support User security; passwords, login, authentication and authorisation, that sort of thing so am using the SaltedHashLoginGenerator detailed here.

I think I need to tweak it. I don't like email addresses as usernames for example but those are minor. My big problem is Rails functional tests.

I used the Rails scaffold generator to set up a framework for my own model/view/controller and it very kindly generated a complete set of model/unit and controller/functional tests for me. Unfortunately my functional tests all failed because every method required a valid user to log in first, thanks to Salty and the use of before_filters.

The problem is described here. Lots of people trying to use the setup method of the TestCase to either login or assign a user object to the session. Unfortunately setup seems to be broken as of Rails 2.0.2. Ticket #10568 has it fixed, but I'm not sure it hasn't broken something else, and I don't yet know how to apply a changeset without waiting for 2.0.3 anyway.

Here's how I've fixed it for me. I'll just demo using one method. The original test looked like

def test_should_create_contact
assert_difference('Contact.count') do
post :create, :contact => { :firstname => "tester" }
end

assert_redirected_to contact_path(assigns(:contact))
end

I found out that the post (along with get, out, delete and head) can take an optional 3rd parameter, a hash of key/value pairs which populate the test's session object. Since SaltedHashLoginGenerator's user_controller simply checks for a key called 'user' defining a User model object we can write

def test_should_create_contact
assert_difference('Contact.count') do
post :create, { :contact => { :firstname => "tester" } }, { 'user' => User.find(1000001) }
end

assert_redirected_to contact_path(assigns(:contact))
end

This ensures that the session object access by the controller has 'user' key with a valid User object, which keeps the user_controller happy.

Note, at first I tried to alter the line, so:

post :create, :contact => { :firstname => "tester" }, 'user' => User.find(1000001)

just adding on the last parameter after a comma. This variously ended up with an empty session and/or syntax errors as I messed about with strings, symbols and braces. In the line shown, the session turned out to be empty because it though 'user' was the second entry in the params hash, which is the second argument of the post method, i.e. the one hash contained two keys, :contact and 'user'. You wouldn't believe how long that took to track down!