Using url_for in Ruby on Rails tests
On a recent Ruby on Rails project, we ran into a situation where we needed to use the ActionController::Base url_for method in our tests. My first attempt was to simply write the test as follows:
def test_next__should_show_step3 @request.env["HTTP_REFERER"] = @controller.url_for( :controller => 'wizard', :action => 'step', :step_number => "1", :id => 1) post :next, :id => 1 assert_not_nil assigns(:virtual_tour) assert_response :redirect assert_redirected_to :action => 'step', :step_number => "3", :id => assigns(:virtual_tour).id end
When I ran the test it resulted in the following error:
test_next__should_show_step3(VirtualTourWizardControllerTest): NoMethodError: You have a nil object when you didn't expect it! The error occured while evaluating nil.rewrite /usr/local/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_controller/base.rb:488:in `url_for' ./test/functional/virtual_tour_wizard_controller_test.rb:91:in `set_referer_step' ./test/functional/virtual_tour_wizard_controller_test.rb:64:in `test_next__should_update_virtual_tour'
When we looked at the rails code, we saw that an instance variable @url was not initialized yet in our tests. This was causing the nil error seen above. We found a post that wrote about a work around by initializing this variable by calling get :index or some other action that doesn’t cause a side effect. This seems like a bit of a hack so we dug a little further.
After a little digging in the rails source code, we found the class that does the actual URL rewriting. The class is called ActionController::UrlRewriter which url_for delegates to to do the actual URL rewriting. So we created a little helper method in test_helper.rb to wrap ActionController::UrlRewriter in a nice helper method called url_for. The following is the method we created in test_helper.rb
def url_for(options) url = ActionController::UrlRewriter.new(@request, nil) url.rewrite(options) end
We can know rewrite our test to look like this.
def test_next__should_show_step3 @request.env["HTTP_REFERER"] = url_for( :controller => 'wizard', :action => 'step', :step_number => "1", :id => 1) post :next, :id => 1 assert_not_nil assigns(:virtual_tour) assert_response :redirect assert_redirected_to :action => 'step', :step_number => "3", :id => assigns(:virtual_tour).id end
We ended up doing a little more refactoring after this since we set the referer like this in many of our other tests.
Trackbacks
Use this link to trackback from your own site.
thanks a lot, very helpful. I added this to my test_helper
def url_for options
url = ActionController::UrlRewriter.new(@request, nil)
# rewrite the url, but we don’t want the protocol and host part, only the actual path
url.rewrite(options.merge( :protocol => ‘’, :host => ‘’ ))
end unless defined? url_for
I erase the protocol and host so that the returned path does NOT have the http://example.com/ part, which gets in the way.
May not be the best way to do it, but seems to fill me needs.
actually, merging in
nly_path => true is a better solution than overwriting the protocol and host parts.
Thanks Justin. We created the bare minimum here. If you want url_for to work exactly like ActionController::Base I suggest that you take a look at the ActionController::Base.url_for source code. There is a little more checking on whether the parameter is a String, Hash or Symbol. It also provides a way to provide default options.
It is a shame that all of this behavior is not encapsulated. It may be a good patch to extract the url_for method into a helper class that ActionController::Base uses. We could then use the same helper class in our tests and ensure the functionality between ActionController::Base.url_for and the test_help.url_for remain identical.
Maybe I’ll bring that up on the Ruby on Rails Core team mailing list to see if it would be worth my time.