Ruby on Rails has some excellent test infrastructure, and integration testing is among its slicker components.
However, my own experience suggests that OpenID can show some problems in Rails’ ability to test controllers with mocking (a la Mocha).
If you use ruby-openid and open_id_authentication, as does the multi-OpenID per user test app I mentioned earlier, you don’t want to test OpenID logins for real. That would actually query an external server, which is both slower and less reliable than good tests should be.
Looking through the open_id_authentication code, it makes sense to replace the method authenticate_with_open_id() with a stub that yields appropriate parameters, automatically giving success, if called with the correct parameters.
After lots of ugly trial and error, I determined that Rails was instantiating a new controller for each request, so even if I could get hold of the appropriate controller instance it wouldn’t do me any good. I seriously considered monkeypatching the code for Rails integration test instances to add a hook that let me mock the controller — Mocha will easily produce the kind of stub function this wants, it’s just hard to get hold of the instance to add it.
Eventually, I just gave up and changed the class itself, SessionsController, to add the new stub version of authenticate_with_open_id. You can restore it afterward (I do below) so it shouldn’t cause you problems in later testing. It would be easy to abstract this into a function that took a block parameter, and only changed SessionsController for the code within that block. I won’t do that here, but it’s a straightforward addition.
Without further ado, here’s the (ugly) code I used:
class RegisterTest < ActionController::IntegrationTest
def test_new_openid_account
user = users(:jwz)
session = new_session_as(:jwz)
SessionsController.class_eval <<END_OF_EVAL
alias :old_authenticate_with_open_id :authenticate_with_open_id
def authenticate_with_open_id(*args)
# Faked 'success' object
result = Object.new()
result.instance_eval {
def successful?
true
end
}
registration = { :login => "#{user.login}", :email => "#{user.email}" }
yield [result, "#{user.user_openids.first.openid_url}", registration]
end
END_OF_EVAL
session.openid_login_as(:jwz)
SessionsController.class_eval {
alias :authenticate_with_open_id :old_authenticate_with_open_id
}
end
end
module TestMixin
# Caution: stub before using this method or otherwise make sure that
# OpenID will succeed without further interaction...
def openid_login_as(person, redir_to = "home/index")
person = users(person)
post sessions_path, :which_service => "openid",
:service_login => person.user_openids.first.openid_url
assert_select "div#flash_error", :count => 0
assert_response :redirect
follow_redirect!
assert_template redir_to
end
def new_session
open_session do |session|
session.extend(TestMixin)
yield session if block_given?
end
end
def new_session_as(person)
new_session do |session|
session.get_login_page
session.log_in_as(person)
yield session if block_given?
end
end
end
Note that there’s a little more code that strictly necessary because I’ve used a little DSL testing language, rather like Jamis Buck’s post on integration testing above. The big class_eval with inlined strings is more than a bit messy, but it works for me. May you also have good luck with it.