Plasticx Blog

Capable of being shaped or formed

assert_redirected_to Expects Strings, Not Symbols and Simple Localization

Posted by Mike 10/28/2006 at 05:21PM

I’ve been in the process of refactoring a CRUD-like webapp that I had written in PHP over to the Ruby On Rails Framework. I’m almost done with the re-factor of the object model and views and started going through A Guide to Testing the Rails to learn the Rails way of unit testing to make my code even better. I was on sub-chapter 9.4 Response-Related Assertions and came upon an error in my unit test of a controller I couldn’t quite figure out. The syntax looked right, the controller logic looked right, and the test logic looked right.

This is a recreation of the error I kept getting with the assert_redirected_to test:


$ ruby test/functional/foo_controller_test.rb
Loaded suite test/functional/foo_controller_test
Started
F
Finished in 0.029546 seconds.

1) Failure:

test_invalid_language(FooControllerTest) [test/functional/foo_controller_test.rb:20]:
response is not a redirection to all of the options supplied (redirection is <{:action=>"index", :language=>"es", :controller=>"foo"}>), difference: <{:action=>:index, :controller=>"foo"}>

1 tests, 4 assertions, 1 failures, 0 errors

This controller is using a before filter to re-establish a localization state if the user’s preferred language that is stored in a cookie exists. For simplicity’s sake the webapp has a simple localization framework to render pages into different languages from translations stored in the db. The way it works is if by opening the URL http://localhost:3000/foo/index/fr the page at that URL will be localized into French language. When that page is rendered the webapp sets a long lasting cookie to the save the French language preference. The pattern for the URL is ‘:controller/:action/:language’ which has been defined in the webapp’s configuration.

config/routes.rb

  # The localized path the webapp uses
  map.connect ':controller/:action/:language',
              :controller => 'foo',
              :requirements => { :language => /en|es|fr/ }

This localization scheme follows the suggested pattern of URIs, Addressability, and the use of HTTP GET and POST suggested by the W3C. Specifically the webapp exhibits The Benefits of URI Addressability

  • linking
  • bookmarking
  • caching

I want the controller to be smart so that if the language setting gets corrupted somehow then a default language will get set in a before_filter that I’ve called set_language

Here’s how I created the Foo controller for this example.


$ ruby script/generate controller Foo
exists app/controllers/
exists app/helpers/
create app/views/foo
exists test/functional/
create app/controllers/foo_controller.rb
create test/functional/foo_controller_test.rb
create app/helpers/foo_helper.rb

This is a basic Foo controller wired to be aware of the localization language setting.

class FooController < ApplicationController
  DEFAULT_LANGUAGE = "en"
  VALID_LANGUAGES = %w{en es fr}
  before_filter :set_languageclass FooController < ApplicationController
DEFAULT_LANGUAGE = "en"
VALID_LANGUAGES = %w{en es fr}
before_filter :set_language

def index end

private
def set_language
cookie_expire = 10.years # 10 year cookie
cookie_language = DEFAULT_LANGUAGE
# get the cookie straight
if !cookies[:language].nil? && VALID_LANGUAGES.include?(cookies[:language])
# honor the inbound cookie, change it in the params check
cookie_language = cookies[:language]
end
# now check the language in the GET
if VALID_LANGUAGES.include?(params[:language])
cookie_language = params[:language]
end
cookies[:language] = { :value => cookie_language, :expires => cookie_expire}

# redirect on a bad language if !VALID_LANGUAGES.include?(params[:language]) redirect_to :controller => controller_name, :action => params[:action], :language => cookie_language end end

end

And the example unit test

require File.dirname(__FILE__) + '/../test_helper'
require 'foo_controller'require File.dirname(FILE) + /../test_helper
require foo_controller

# Re-raise errors caught by the controller.
class FooController; def rescue_action(e) raise e end; end

class FooControllerTest < Test::Unit::TestCase
def setup
controller</span> = <span class="co">FooController</span>.new <span class="iv">request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
end

# test if a bad language is set in path, use the cookie setting if not def test_invalid_language @request.cookies[language] = CGI::Cookie.new(language, es) get :index, language => ru assert_not_nil cookies[language] assert_equal %w{es}, cookies[language] assert_redirected_to :action => :index, :language => es #assert_redirected_to :action => ‘index’, :language => ‘es’ assert_response :redirect end

end

Notice the first assert_redirected_to that is causing the error with this unit test and the commented correct assert_redirected_to.

assert_redirected_to :action => :index, :language =>  'es'
#assert_redirected_to :action => 'index', :language => 'es'

The first version causing the error is a nuby mistake on my part. When code is written in a controller that has ActionController::Base as its ancestor, the redirect_to (which calls url_for ) is a method of controller because of inheritance. That controller action’s are really just public methods of the controller class and can be referenced by their symbol. I used the patterns I’ve seen in controller code for redirect_to and url_for to write my assert_redirected_to test.

But the unit test inherits from Test::Unit::TestCase, it is not a child of ActionController::Base. So the assert_redirected_to shouldn’t use the symbol to refer to the index action. It should use the name of the action as a string, here’s the proper code snippet that was commented out above.

assert_redirected_to :action => 'index', :language => 'es'

And now the functional test works correctly.


$ ruby test/functional/foo_controller_test.rb
Loaded suite test/functional/foo_controller_test
Started
.
Finished in 0.016737 seconds.

1 tests, 5 assertions, 0 failures, 0 errors

Posted in , , |

Trackbacks<

Use the following link to trackback from your own site:
http://plasti.cx/trackbacks?article_id=21

  1. Sergei Yakovlev
    01/16/2007 at 09:33AM

    You might want to use

    10.years.since

    instead of

    Time.now + (60 * 60 * 24 * 365 * 10)

    Cheers

  2. Mike Mondragon
    01/16/2007 at 11:32AM

    Weird, hopefully Sergei will come back and elaborate as there isn’t a years attribute in the Fixnum class.
    http://www.ruby-doc.org/core/classes/Fixnum.html

  3. ymerej
    01/25/2007 at 11:35AM

    the years method (and some others like it) is added to fixnum by rails.

  4. Mike Mondragon
    01/25/2007 at 10:00PM

    This is how a java programmer does all the seconds in 10 years :)


    cookie_expire = Time.now + (60 * 60 * 24 * 365 * 10) # 10 year cookie

    And this is how a rails programmer does it:


    cookie_expire = 10.years # 10 year cookie

    The Fixnum class is extended in Active Support, see pages 251-252 of Agile Web Development with Rails, 2nd edition. Thanks for the pointers Sergei Yakovlev and ymerej

  5. Michelle
    12/13/2007 at 06:32PM

    Hi,
    Does anyone knows anything about Schema Manipulation Outside Migrations ? From what I’ve read, methods can be saved in a private method in the model or could be
    implemented in a library.

    I was wandering if it is applicable for all methods that you can create in migrations?


Web Statistics