Reputation: 23347
I'd like to test if my Rails controller sets a valid options (in this case path
) for cookie. How could I do it with RSpec?
My code:
#Controller
def action
#(...)
cookies[:name] = { value:cookie_data,
path: cookie_path }
#(...)
end
#spec
it "sets cookie path" do
get 'action'
#I'd like do to something like that
response.cookies['name'].path.should == '/some/path'
end
Upvotes: 1
Views: 1605
Reputation: 6287
I've tried several of the solutions offered here and in similar threads. The only one that has worked well for me is to inspect the Set-Cookie header, something like this:
it 'expires cookie in 15 minutes' do
travel_to(Date.new(2016, 10, 25))
post 'favorites', params: { flavor: 'chocolate' }
travel_back
details = 'favorite=chocolate; path=/; expires=Tue, 25 Oct 2016 07:15:00 GMT; HttpOnly'
expect(response.header['Set-Cookie']).to eq details
end
This is a bit brittle, in that other, non-critical attributes of the cookie may break this string. But it does keep you out of Rails internals, and lets you check several attributes at once. (Noting that's an anti-pattern rspec!)
If you only care about one attribute, you could match it like this:
expect(response.header['Set-Cookie']).to match(
/favorite=chocolate.*; expires=Tue, 25 Oct 2016 07:15:00 GMT/
)
Upvotes: 0
Reputation: 309
After trying unsuccessfully to get CGI::Cookie.parse to do the right thing, I wound up rolling my own parser. It's quite simple:
def parse_set_cookie_header(header)
kv_pairs = header.split(/\s*;\s*/).map do |attr|
k, v = attr.split '='
[ k, v || nil ]
end
Hash[ kv_pairs ]
end
Here's a sampling of the results it produces:
Cookie creation:
IN: "signup=VALUE_HERE; path=/subscriptions; secure; HttpOnly"
OUT: {"signup"=>"VALUE_HERE", "path"=>"/subscriptions", "secure"=>nil, "HttpOnly"=>nil}
Cookie deletion:
IN: "signup=; path=/subscriptions; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000; secure; HttpOnly"
OUT: {"signup"=>nil, "path"=>"/subscriptions", "max-age"=>"0", "expires"=>"Thu, 01 Jan 1970 00:00:00 -0000", "secure"=>nil, "HttpOnly"=>nil}
And here's an example spec to go along with it:
describe 'the Set-Cookie header' do
let(:value) { 'hello world' }
let(:signup_cookie) do
parse_set_cookie_header response.header['Set-Cookie']
end
before do
get :index, :spec => 'set_signup_cookie'
end
it 'has a payload set for :signup' do
expect(signup_cookie['signup']).to be_present
end
it 'has the right path' do
expect(signup_cookie['path']).to eq '/subscriptions'
end
it 'has the secure flag set' do
expect(signup_cookie).to have_key 'secure'
end
it 'has the HttpOnly flag set' do
expect(signup_cookie).to have_key 'HttpOnly'
end
it 'is a session cookie (i.e. it has no :expires)' do
expect(signup_cookie).not_to have_key 'expires'
end
it 'has no max-age' do
expect(signup_cookie).not_to have_key 'max-age'
end
end
Upvotes: 3
Reputation: 23347
I've found a solution, but it seems to be a kind of hacking. I wonder if there is a cleaner way to do it.
it "sets cookie path" do
get 'action'
match = response.header["Set-Cookie"].match(/path=(.*);?/)
match.should_not be_nil
match[1].should == '/some/path'
end
Upvotes: 0