Session variables with Cucumber Stories

2020-02-16 20:29发布

I am working on some Cucumber stories for a 'sign up' application which has a number of steps.

Rather then writing a Huuuuuuuge story to cover all the steps at once, which would be bad, I'd rather work through each action in the controller like a regular user. My problem here is that I am storing the account ID which is created in the first step as a session variable, so when step 2, step 3 etc are visited the existing registration data is loaded.

I'm aware of being able to access controller.session[..] within RSpec specifications however when I try to do this in Cucumber stories it fails with the following error (and, I've also read somewhere this is an anti-pattern etc...):

Using controller.session[:whatever] or session[:whatever]

You have a nil object when you didn't expect it!
The error occurred while evaluating nil.session (NoMethodError)

Using session(:whatever)

wrong number of arguments (1 for 0) (ArgumentError)

So, it seems accession the session store isn't really possible. What I'm wondering is if it might be possible to (and I guess which would be best..):

  1. Mock out the session store etc
  2. Have a method within the controller and stub that out (e.g. get_registration which assigns an instance variable...)

I've looked through the RSpec book (well, skimmed) and had a look through WebRat etc, but I haven't really found an answer to my problem...

To clarify a bit more, the signup process is more like a state machine - e.g. the user progresses through four steps before the registration is complete - hence 'logging in' isn't really an option (it breaks the model of how the site works)...

In my spec for the controller I was able to stub out the call to the method which loads the model based on the session var - but I'm not sure if the 'antipattern' line also applies to stubs as well as mocks?

Thanks!

11条回答
霸刀☆藐视天下
2楼-- · 2020-02-16 20:30

@Ajedi32 I ran into the same issue (undefined method 'current_session' for Capybara::RackTest::Driver) and putting this in my step definition fixed the problem for me:

rack_test_browser = Capybara.current_session.driver.browser

cookie_jar = rack_test_browser.current_session.instance_variable_get(:@rack_mock_session).cookie_jar
cookie_jar[:stub_user_id] = @current_user.id

In my controller action, I referred to cookies[:stub_user_id], instead of cookie_jar[:stub_user_id]

查看更多
相关推荐>>
3楼-- · 2020-02-16 20:34

Why don't you use FactoryGirl or (Fixjour or Fabricator) with Devise (or Authlogic) and SentientUser? Then you can simply sniff which user is already logged in!

@user = Factory(:user)       # FactoryGirl
sign_in @user                # Devise
User.current.should == @user # SentientUser
查看更多
爱情/是我丢掉的垃圾
4楼-- · 2020-02-16 20:35

I use a testing-only sign-in solution like Prikka's, but I do it all in Rack instead of creating a new Controller and routes.

# in config/environments/cucumber.rb:

config.middleware.use (Class.new do
  def initialize(app); @app = app; end
  def call(env)
    request = ::Rack::Request.new(env)
    if request.params.has_key?('signed_in_user_id')
      request.session[:current_user_id] = request.params['signed_in_user_id']
    end
    @app.call env
  end
end)

# in features/step_definitions/authentication_steps.rb:
Given /^I am signed in as ([^\"]+)$/ do |name|
  user = User.find_by_username(name) || Factory(:user, :username => name)
  sign_in_as user
end

# in features/step_definitions/authentication_steps.rb:
Given /^I am not signed in$/ do
  sign_in_as nil
end

module AuthenticationHelpers
  def sign_in_as(user)
    return if @current_user == user
    @current_user = user
    get '/', { 'signed_in_user_id' => (user ? user.to_param : '') }
  end
end

World(AuthenticationHelpers)
查看更多
等我变得足够好
5楼-- · 2020-02-16 20:37

Another slight variation:

# In features/step_definitions/authentication_steps.rb:

class SessionsController < ApplicationController
  def create_with_security_bypass
    if params.has_key? :user_id
      session[:user_id] = params[:user_id]
      redirect_to :root
    else
      create_without_security_bypass
    end
  end

  alias_method_chain :create, :security_bypass
end

Given %r/^I am logged in as "([^"]*)"$/ do |username|
  user = User.find_by_username(username) || Factory(:user, :username => username)
  page.driver.post "/session?user_id=#{user.id}"
end
查看更多
我命由我不由天
6楼-- · 2020-02-16 20:39

Re: Ryan's solution:

Does not work with Capybara, unless small adaptation done:

rack_test_driver = Capybara.current_session.driver
cookie_jar = rack_test_driver.current_session.instance_variable_get(:@rack_mock_session).cookie_jar
@current_user = Factory(:user)
cookie_jar[:stub_user_id] = @current_user.id

(found here: https://gist.github.com/484787)

查看更多
\"骚年 ilove
7楼-- · 2020-02-16 20:43

My understanding is that you get:

You have a nil object when you didn't expect it!
The error occurred while evaluating nil.session (NoMethodError)

when session[] is accessed before request has been instantiated. In your case, I'd imagine if you put webrats' visit some_existing_path before accessing session[] in your step defenition, the error will go away.

Now, unfortunately, session doesn't seem to persist across steps (at least, I couldn't find the way), so this bit of information doesn't help to answer your question :)

So, I suppose, Ryan's session[:user_id] = cookies[:stub_user_id]... is the way to go. Although, imo, test related code in the application itself doesn't sound right.

查看更多
登录 后发表回答