[cucumber] accessing session variables

Hey,

I have the following simple scenario and the step definitions:

Scenario: Create album
Given I am logged in as “pepito”
When I go to my profile page

Given /^the user (.*) exists$/ do |login_name|
User.find_by_login(login_name) || Factory(:user_with_password, :login
=> login_name)
end

Given /^I log in as (.*)$/ do |login_name|
user = User.find_by_login(login_name)

it is supposed that the user was generated by the

:user_with_password fixture

that has the ‘secret’ password

post “/session”, :login => user.login, :password => ‘secret’
end

Given /^I am logged in as “(.*)”$/ do |login_name|
Given “the user #{login_name} exists”
Given “I log in as #{login_name}”
end

When /^I go to (.+)$/ do |page_name|
visit path_to(page_name)
end

def path_to(page_name)
case page_name

when /my profile page/i
member_profile_path(:id => session[:user_id])

end

I receive the following error:

When I go to my profile page #
features/step_definitions/webrat_steps.rb:6
You have a nil object when you didn’t expect it!
The error occurred while evaluating nil.session (NoMethodError)
/usr/local/lib/ruby/gems/1.8/gems/actionpack-2.2.2/lib/action_controller/test_process.rb:429:in
`session’

So it seems like the session is not accessible -or not this way- in the
step definitions. I finally came up with that workaround:

def path_to(page_name)
case page_name

when /my profile page/i
member_profile_path(:id => User.first)

end

However, I am not at all content with this hack. It does not go to the
profile page of the logged in user but to the first one. This can be the
same -and in this scenario it is- but there is no guarantee for that.

I wonder if there is a way to retrieve things stored in the session.
Ideally I could even write:

when /my profile page/i
member_profile_path(:id => current_user)

Where current_user is defined in a helper class, and will also use a
session variable behind the scenes (I received an undefined method
“current_user” error when I tried this).

Could someone enlighten me about how to do this or whether this is
considered bad practice because current_user is more state-based (as
opposed to behavior-based_?

Thank you,
Balint

On 11 Mar 2009, at 18:52, Balint E. wrote:

User.find_by_login(login_name) || Factory(:user_with_password, :login

case page_name
You have a nil object when you didn’t expect it!
case page_name

when /my profile page/i
member_profile_path(:id => User.first)

end

However, I am not at all content with this hack. It does not go to the
profile page of the logged in user but to the first one. This can be
the
same -and in this scenario it is- but there is no guarantee for that.

IMO, trying to access implementation details like sessions from your
acceptance tests is an anti-pattern, to be avoided.

The puzzle you’ve hit here is that there is state implied in your
scenarios by the use of the phrase “my profile page”. I think there
are two approaches this:

(1) You remove the implicit state from the scenario by re-writing your
step as “When I go to the profile page for the User”. This means that
you can safely now use User.first, and you can even assert at the top
of that step that User.count.should == 1, because the step refers to
“the User” - so there must only be one, right?

(2) You model the state implicit in the scenario within your tests by
writing an instance variable on the Scenario’s world when you log in -
something to represent “me”.

There is a third approach where you write a special ‘API’ on your app
which the tests can use to find out who “me” is. That might be as
simple as conventionally putting it in a HTML comment on the page, or
even displaying it on every page, or as over-engineered as a REST GET
request to a user/current or /session resource.

However you chose to do it, I would urge you to avoid going under the
covers to grab the session object directly - it’s a path to hideous
complexity IMO. Better to keep your test layer as separate as you can
from the code.

Does that make sense?

Matt W.
http://blog.mattwynne.net

On 14 Mar 2009, at 19:11, Balint E. wrote:

nice

  1. using instance variables to pass state in the course of a features
    makes step definitions too coupled. (I have not experienced this but
    read it in the thread and it seems logical)

I think you have to be pragmatic about this. We have a @user variable
in our steps that represents ‘me’ and that has worked OK for us. I
think that’s just about the only time we’ve had to use instance
variables in 900 or so scenarios, but there are certainly times when
it makes sense. You just have to be aware of the risks and not use
them too cheaply.

Matt W.
http://blog.mattwynne.net

Oh, I see. I’ll heed your advice and will consider using @user for the
logged in user in the future.

Thanks again,
Balint

Matt W. wrote:

On 14 Mar 2009, at 19:11, Balint E. wrote:

nice

  1. using instance variables to pass state in the course of a features
    makes step definitions too coupled. (I have not experienced this but
    read it in the thread and it seems logical)

I think you have to be pragmatic about this. We have a @user variable
in our steps that represents ‘me’ and that has worked OK for us. I
think that’s just about the only time we’ve had to use instance
variables in 900 or so scenarios, but there are certainly times when
it makes sense. You just have to be aware of the risks and not use
them too cheaply.

Hey Matt,

Thanks a lot for your elaborated response!

After reading this excellent thread about how to pass state between
steps:

http://www.mail-archive.com/[email protected]/msg06296.html

I realized

  1. I am not alone in this boat :slight_smile:
  2. that if I write “I go to”, “my profile page” there is an implicit
    assumption -as you note below- that there is only one user so there is
    no harm in using this assumption in my steps. If there are several users
    in a scenario then I have to explicitly name users (“when Alice goes to
    her profile page”, "when Bob presses the “Send” button, etc.)

In both cases my steps remain reusable, in fact. What does not seem nice
to me -and what I wanted to evade- is to write this kind of feature:

Scenario: Create album from scratch
Given I am logged in as “pepito”
When “pepito” goes to his photos page
And “pepito” follows “New album”

It seems redundant. Once I know I am pepito in the feature I should not
have to define it in each step. It’s like with spoken language.

  1. using instance variables to pass state in the course of a features
    makes step definitions too coupled. (I have not experienced this but
    read it in the thread and it seems logical)

That said, I am still a starter with cucumber so I might come back and
reassess my decisions :slight_smile:

Once again, thank you for your suggestions!
Balint

Matt W. wrote:

On 11 Mar 2009, at 18:52, Balint E. wrote:

User.find_by_login(login_name) || Factory(:user_with_password, :login

case page_name
You have a nil object when you didn’t expect it!
case page_name

when /my profile page/i
member_profile_path(:id => User.first)

end

However, I am not at all content with this hack. It does not go to the
profile page of the logged in user but to the first one. This can be
the
same -and in this scenario it is- but there is no guarantee for that.

IMO, trying to access implementation details like sessions from your
acceptance tests is an anti-pattern, to be avoided.

The puzzle you’ve hit here is that there is state implied in your
scenarios by the use of the phrase “my profile page”. I think there
are two approaches this:

(1) You remove the implicit state from the scenario by re-writing your
step as “When I go to the profile page for the User”. This means that
you can safely now use User.first, and you can even assert at the top
of that step that User.count.should == 1, because the step refers to
“the User” - so there must only be one, right?

(2) You model the state implicit in the scenario within your tests by
writing an instance variable on the Scenario’s world when you log in -
something to represent “me”.

There is a third approach where you write a special ‘API’ on your app
which the tests can use to find out who “me” is. That might be as
simple as conventionally putting it in a HTML comment on the page, or
even displaying it on every page, or as over-engineered as a REST GET
request to a user/current or /session resource.

However you chose to do it, I would urge you to avoid going under the
covers to grab the session object directly - it’s a path to hideous
complexity IMO. Better to keep your test layer as separate as you can
from the code.

Does that make sense?

Matt W.
http://blog.mattwynne.net
http://www.songkick.com