Testing Cookies in Rails 2.3

Posted by Andreas on Monday, March 30, 2009 at 15:37 (CEST)

While moving some applications to Rails 2.3 recently, I stumbled across some problems with testing cookies. E.g. this blog uses cookies to remember your name, email address and web url if you leave a comment. This way, you don’t need to re-enter these information the next time you leave a comment. These cookies are set by the controller action that creates new comments (in CommentsController#create):

1 cookies['blog_visitor_name'] = { :value => @comment.name, :expires => 1.year.from_now }

This is the extended form of setting a cookie (a hash is used to not only set the cookies value but also the expiration time. There are several more hash options available which are described in the ActionController#cookies documentation. The basic form (which only sets a simple session cookie that is valid until the visitor closes the browser) uses a simple string:

1 
2 
3 cookies['cookie_name'] = 'cookie_value'

This form of setting and getting cookies in a controller has been there for quite some time and actually hasn’t changed in Rails 2.3. But what has changed, is the availability of cookies in functional tests. Before Rails 2.3, you needed to create a CGI::Cookie object for each cookie of a request. After the request, you could then check the cookies accessor and assert correct attributes of a cookie:

1 def test_comment_creation_overwrites_visitor_cookies
2   @request.cookies['blog_visitor_name'] = CGI::Cookie.new('blog_visitor_name', 'Fred F.')
3   post :create, { ... }
4   assert_equal 'Fred', cookies['blog_visitor_name'].value
5   assert_equal '/', cookies['blog_visitor_name'].path
6   assert cookies['blog_visitor_name'].expires > 364.days.from_now
7 end

As you can see, this test first sets a cookie by creating a CGI:Cookie object and requests the create action. This simulates a user who previously used “Fred F.” as his name and now posts a new comment using the name “Fred”. After processing the request, the test checks if the user’s cookie is update to the new name he gave. Furthermore it tests that the newly created cookie attributes (path and expire time) are properly set.

With Rails 2.3, usage of cookies in tests were modified to match usage in the controller itself. The above test now looks like this:

1 def test_comment_creation_overwrites_visitor_cookies
2   @request.cookies['blog_visitor_name'] = 'Fred F.'
3   post :create, { ... }
4   assert_equal 'Fred', cookies['blog_visitor_name']
5   #assert_equal '/', cookies['blog_visitor_name']...???
6   #assert cookies['blog_visitor_name']...??? > 364.days.from_now
7 end

Since the cookies accessor now returns the cookie value only (like in controllers), it is easier to test wether a cookie was correctly set to the expected value. However, extended information like the cookie path and the expiration time are not available anymore and therefore we cannot test anymore if these attributes were correctly set by the controller.

So how can you test cookie attributes with Rails 2.3? Unfortunately you can’t. The cookies accessor in tests parses the content of the Set-Cookie header of a response and builds a hash of cookies which were set by the controller action. Unfortunately, it only gathers the cookie name and value and does not parse the rest of the data. The only way that I know of to test cookie attributes is to manually parse the contents of the Set-Cookie header for now.

17 comments

Gravatar
Beren wrote 9 days later:

Thanks – good info!

Just a note, there’s a missing quote in this line:

cookies[‘cookie_name] = ’cookie_value’

Gravatar
Andreas wrote 9 days later:

Oops. Corrected, thanks.

Gravatar
Andreas wrote 10 days later:

Btw, Rails 2.3.2 html-escapes values of cookies in tests, which is a bug and will be fixed in 2.3.3

Gravatar
Kirill Maximov wrote 12 days later:

Thanks a lot for this article! I faced exactly this problem when converting my site to rails 2.3.

Gravatar
grosser wrote 2 months later:

Thanks from me too, exactly the problem i had :D

Gravatar
Faye wrote 3 months later:

Thanks for the info!

Any idea when Rails 2.3.3 will be released? I don’t see it listed here as of June 28, 2009:

http://rubyforge.org/frs/?group_id=307

Gravatar
Andreas wrote 3 months later:

Good question. Accordingly to rails.lighthouseapp.com (the Rails bugtracker), there’s no open 2.3.3 milestone, so it looks like that version is complete. However there doesn’t seem to be a gem or official announcement out yet.

Gravatar
KonstantinMiller wrote 3 months later:

How soon will you update your blog? I’m interested in reading some more information on this issue.

Gravatar
Allan Rencontres wrote 3 months later:

“The only way that I know of to test cookie attributes is to manually parse the contents of the Set-Cookie header for now.” It takes a bit of time, it’s true, but in the end it is not all that tragic.

Gravatar
joj wrote 3 months later:

2.3.3 ad portas: Prepare version numbers, changelogs and gem dependencies for 2.3.3.

note the date in those changelogs:

2.3.3 (July 12, 2009)

Gravatar
Curtis wrote 4 months later:

One gotcha that I just recovered from and have not seen documented is that although you can set a cookie in ApplicationController using symbols, you can only retrieve it using ApplicationController::TestCase using strings.

So
cookie[:name] = {:value => "Fred", :expires => 1.year_from_now}
works,
assert_equal "Fred", cookie[:name]
will not as cookie[:name] is nil

Gravatar
Sharad Jain wrote 4 months later:

Nice tips. I also noticed that setting cookies in test using symbols don’t work as expected. You need to use string for cookie-hash-keys instead of symbols.

Gravatar
Kristal L. Rosebrook wrote 4 months later:

Very nice tips!

Gravatar
Morgan schweers wrote 10 months later:

Greetings,
Sorry for the necromancy, but I just had this problem, and it drove me to fix it. This article is the top result for this problem, so I thought I’d help (hopefully) by providing a method to use to replace #cookies if you need to test cookie expirations, paths, domains, etc… Add this to your RAILS_ROOT/test/test_helper.rb.

class ActionController::TestCase
  def better_cookies
    cookies = {}
    Array(@response.headers['Set-Cookie']).each do |cookie|
      key = nil
      details = cookie.split(';').inject({}) do |fields, cookie_field|
        pair = cookie_field.split('=').map {|val| Rack::Utils.unescape(val.strip)}
        key = pair.first unless key
        fields.merge(pair.first => pair.last)
      end
      details['expires'] = Time.parse(details['expires']) if details['expires']
      cookies[key] = details
    end
    cookies
  end
end

Then you use it with something like…

assert better_cookies['token']['expires'].present? &&
       better_cookies['token']['expires'] > 13.days.from_now

If you want to use symbols, replace all {} with HashWithIndifferentAccess.new and it should allow symbol or string accesses.

I hope that helps the next person who comes along!

— Morgan Schweers, CyberFOX!

p.s. On my display, the fixed font cuts off the bottom few pixels. The text is right if you select it, but it looks wrong in places, especially where underscore characters are used. See it on GitHub.

Gravatar
Andreas wrote 10 months later:

Thanks for sharing this solution, Morgan.

p.s. I fixed the line-height in code blocks, so underscores should display fine again now.

Gravatar
Mercedes wrote 12 months later:

Wow. zargony.com is killer.

Gravatar
Infotounfinly wrote 12 months later:

Visit LowCostLinks.com For 100 FREE Links Pointing To Your Website Of Choice!

Should You Need 1,000 Or More Links To Your Site – We Sell Packages Of 1,000 Links For As Little As $6 USD.

Let The Linking Begin!

Leave a comment

(required)

(required; will not be published)

(optional)

(required; Textile formatting allowed)