Friday, September 28, 2007

Rails Integration Testing

I found this to be a pretty good (well, there's not really much info out there) intro to writing integration tests.

But if you look in the comments, people are sort of stumped on how to deal with logging in users when you don't have passwords stored in the clear.

Here's my solution, which injects the user object directly into the request session, meaning that you don't need any other ugly hacks (such as letting your user controller accept hashed passwords.. ugh).



# http://weblog.jamisbuck.org/2006/3/9/integration-testing-in-rails-1-1
# describes some cool integration testing, but there's no
# solution for how to 'inject' a user into the system when all
# you have is hashed passwords.
# The code below lets you inject a user object directly into your
# 'session' object.

# So you can add the following methods to your integration test (see the above blog
# for more explanation):
#
# def new_session_as( user )
# new_session do | sess | # new_session is defined per the blog post above
# sess.user_object = user # aha this is where we inject the user object
# yield sess if block_given?
# end
# end


# allow a user_object to be injected via the HTTP headers
ActionController::Integration::Session.class_eval {
attr_accessor :user_object
alias process_without_user_headers process
def process( method, path, parameters=nil, headers=nil )
unless self.user_object.nil?
headers = (headers || {}).merge( {'X_USER_HACK' => self.user_object } )
end
process_without_user_headers( method, path, parameters, headers )
end
}

# extract the user object from the headers, and store it as an attribute
ActionController::Integration::Session::MockCGI.class_eval {
alias initialize_for_real initialize
attr_accessor :user_object
def initialize( env, input )
user = env.delete( 'HTTP_X_USER_HACK' )
self.user_object = user
initialize_for_real( env, input )
end
}

# extract the user object from the mock cgi class, and
# set it up in our session
ActionController::CgiRequest.class_eval {
alias initialize_for_real initialize
def initialize( cgi, options )
initialize_for_real( cgi,options )
# The following line may be different for you:
self.session['user'] = cgi.user_object
end
}