Examining the Contents of Cookie

Problem

Contributed by: Evan Henshaw-Plath (rabble)

Your application uses cookies. You want to test your application's ability to create and retrieve them with a functional test.

Solution

Most of the time you'll store information in the session, but there are cases when you need to save limited amounts of information in the cookie itself. Create a controller that sets a cookie to store page color:

app/controller/cookie_controller.rb:

class CookieController < ApplicationController
 def change_color
 @page_color = params[:color] if is_valid_color?
 @page_color ||= cookies[:page_color]
 cookies[:page_color] = 
 { :value => @page_color, 
 :expires => Time.now + 1.year,
 :path => '/', 
 :domain => 'localhost' } if @page_color
 end
 private
 def is_valid_color?
 valid_colors = ['blue', 'green', 'black', 'white']
 valid_colors.include? params[:color] 
 end end

Now, create a test that verifies that the values of the cookie are set correctly by the controller:

test/functional/cookie_controller_test.rb:

def test_set_cookie
 post :change_color, {:color => 'blue'}
 assert_response :success
 assert_equal '/', cookies['page_color'].path
 assert_equal 'localhost', cookies['page_color'].domain
 assert_equal 'blue', cookies['page_color'].value.first 
 assert 350.days.from_now < cookies['page_color'].expires end

To fully test cookies, you need to test that your application is not only setting the cookies, but also correctly reading them when passed in with a request. To do that you need to create a CGI::Cookie object and add that to the simulated test Request object, which is set up before every test in the setup method.

test/functional/cookie_controller_test.rb:

def test_read_cookie
 request.cookies['page_color'] = CGI::Cookie.new( 
 'name' => 'page_color',
 'value' => 'black',
 'path' => '/',
 'domain' => 'localhost')
 post :change_color
 assert_response :success
 assert_equal 'black', cookies['page_color'].value.first
 assert 350.days.from_now < cookies['page_color'].expires end

Discussion

If you are using cookies in your application, it is important for your tests to cover them. If you don't test the cookies you use, you will be missing a critical aspect of your application. Cookies that fail to be set or read correctly can prove difficult to track down and debug unless you write functional tests.

In this recipe we've tested both the creation and reading of cookie objects. When you are creating cookies, it's important to test both that they are created correctly and that your controller does the right thing when it detects a cookie. If you only test either the creation or the reading of cookies, you introduce the possibility of undetected and untested bugs.

Cookies in Rails are based on the CGI::Cookie class and are made available in the controller and functional tests via the cookie's object. Each individual cookie appears to be a hash, but it's a special cookie hash that responds to all the methods for cookie options. If you do not give a cookie object an expiration date, it will be set to expire with the browser's session.

A cookie is inserted into the response object for the HTTP headers via the to_s (to string) method, which serializes the cookie. When you are debugging cookies, it is often useful to print the cookie in the breakpointer. When you print the cookie, you can examine exactly what is getting sent to the browser.

irb(test_set_cookie(CookieControllerTest)):001:0> cookies['page_color'].to_s
=> "page_color=blue; domain=localhost; path=/; expires=Mon, 07 May 2007 
 04:38:18 GMT"

When debugging cookie issues, it is often important to turn on your browser's cookie tracking. Tracking can show you what the cookie actually looks like to the browser. Once you have the actual cookie string, you can pass it back in to the cookie object so your tests are driven by real-world test data.

test/functional/cookie_controller_test.rb:

def test_cookie_from_string
 cookie_parsed = CGI::Cookie.parse( 
 "page_color=green; domain=localhost; path=/; " +
 "expires=Mon, 07 May 2007 04:38:18 GMT" )
 cookie_hash = Hash[*cookie_parsed.collect{|k,v| [k,v[0]] }.flatten]
 cookie_hash['name'] = 'page_color'
 cookie_hash['value'] = cookie_hash[cookie_hash['name']]
 request.cookies['page_color'] = CGI::Cookie.new(cookie_hash)
 post :change_color
 assert_response :success
 assert cookies['page_color']
 assert_equal 'green', cookies['page_color'].value.first end

This last test shows you the steps involved in transforming a cookie from a CGI::Cookie object to a string and back again.

It is important to understand when to use cookies and when not to use them. By default, Rails sets a single session id cookie when a user starts to browse the site. This session is associated with a session object in your Rails application. A session object is just a special hash that is instantiated with every request and made accessible in your controllers, helpers, and views. Most of the time you don't want to set custom cookies; just add data or model IDs to the session object instead. This keeps the user from having too many cookies, and conforms with the standards and best practices of the HTTP protocol.

See Also