Testing cookies in Rails

… and check why 5600+ Rails engineers read also this

Testing cookies in Rails

Recently at Arkency I was working on a task, on which it was very important to ensure that the right cookies are saved with the specific expiration time. Obiovusly I wanted to test this code to prevent regressions in the future.

Controller tests

Firstly I thought about controller tests, but you can use only one controller in one test (at least without strong hacks) and in this case it was important to check values of cookies after requests sent into few different controllers. You can now think, that controller tests are “good enough” for you, if you don’t need to reach to different controllers. Not quite, unfortunately. Let’s consider following code:

class ApplicationController
  before_filter :do_something_with_cookies

  def do_something_with_cookies
    puts "My cookie is: #{cookies[:foo]}"
    cookies[:foo] = {
      value: "some value!",
      expires: 30.minutes.from_now,
    }
  end
end

And controller test:


describe SomeController do
  specify do
    get :index

    Timecop.travel(35.minutes.from_now) do
      get :index
    end
  end
end

Note that the cookie time has expiration time of 30 minutes and we are doing second call “after” 35 minutes, so we would expect output to be:

My cookie is:
My cookie is:

So, we would expect cookie to be empty, twice. Unfortunately, the output is:

My cookie is:
My cookie is: some value!

Therefore, it is not a good tool to test cookies when you want to test cookies expiring.

Feature specs

My second thought was feature specs, but that’s capybara and we prefer to avoid capybara if we can and use it only in very critical parts of our applications, so I wanted to use something lighter than that. It would probably work, but as you can already guess, there’s better solution.

Request specs

There’s another kind of specs, request specs, which is less popular than previous two, but in this case it is very interesting for us. Let’s take a look at this test:

describe do
  specify do
    get "/"

    Timecop.travel(35.minutes.from_now) do
      get "/"
    end
  end
end

With this test, we get the desired output:

My cookie is:
My cookie is:

Now we would like to add some assertions about the cookies. Let’s check what cookies class is by calling cookies.inspect:

#<Rack::Test::CookieJar:0x0056321c1d8950 @default_host="www.example.com", 
  @cookies=[#<Rack::Test::Cookie:0x0056321976f010 @default_host="www.example.com", 
    @name_value_raw="foo=some+value%21", @name="foo", @value="some value!", 
      @options={"path"=>"/", "expires"=>"Fri, 02 Jun 2017 22:29:34 -0000", "domain"=>"www.example.com"
}>]>

Great, we see that it has all information we want to check: value of the cookie, expiration time, and more. You can easily retrieve the value of the cookie by calling cookies[:foo]. Getting expire time is more tricky, but nothing you couldn’t do in ruby. On HEAD of rack-test there’s already a method get_cookie you can use to get all cookie’s options. If you are on 0.6.3 though, you can add following method somewhere in your specs:

def get_cookie(cookies, name)
  cookies.send(:hash_for, nil).fetch(name, nil)
end

It is not perfect, but it is simple enough until you migrate to newer version of rack-test. In the end, my specs looks like this:

describe do
  specify do
    get "/"

    Timecop.travel(35.minutes.from_now) do
      get "/"

      cookie = get_cookie(cookies, "foo")
      expect(cookie.value).to eq("some value!")
      expect(cookie.expires).to be_present
    end
  end

  # That will be built-in in rack-test > 0.6.3
  def get_cookie(cookies, name)
    cookies.send(:hash_for, nil).fetch(name, nil)
  end
end

With these I can test more complex logic of my cookies. Having reliable tests allows me and my colleagues to easily refactor code in the future and prevent regressions in our legacy applications (if topic of refactoring legacy applications is interesting to you, you may want to check out our Fearless Refactoring book).

What are your experiences of testing cookies in rails?

You might also like