黄瓜故事的会话变量

我正在为一个“注册”应用程序编写一些Cucumber故事,该应用程序有许多步骤。

而不是写一个Huuuuuuuge故事来同时覆盖所有步骤,这将是不好的 ,我宁愿像普通用户一样在控制器中完成每个动作。 我的问题在于我将在第一步中创建的帐户ID存储为会话变量,因此当访问步骤2,步骤3等时,将加载现有的注册数据。

我知道能够在RSpec规范中访问controller.session[..]但是当我尝试在Cucumber故事中执行此操作时,它失败并出现以下错误(并且,我也读过某个地方这是一个反模式等等…):

使用controller.session [:whatever]或session [:whatever]

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

使用会话(:无论如何)

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

因此,似乎加入会话存储是不可能的。 我想知道的是,是否有可能(我猜哪个会是最好的……):

  1. 模拟会话商店等
  2. 在控制器和存根中有一个方法(例如, get_registration ,它分配一个实例变量……)

我查看了RSpec书(好吧,浏览过)并浏览了WebRat等,但我还没有真正找到问题的答案……

为了进一步澄清,注册过程更像是一个状态机 – 例如用户在注册完成之前通过四个步骤前进 – 因此“登录”实际上不是一个选项(它打破了网站工作原理的模型) )…

在我对控制器的规范中,我能够根据会话var加载对方法的调用 – 但是我不确定’antipattern’行是否也适用于存根和模拟?

谢谢!

在黄瓜情景中,嘲笑很糟糕 – 它们几乎都是反模式。

我的建议是编写一个实际记录用户的步骤。我这样做

 Given I am logged in as "auser@example.com" Given /^I am logged in as "(.*)"$/ do |email| @user = Factory(:user, :email => email) @user.activate! visit("/session/new") fill_in("email", :with => @user.email) fill_in("password", :with => @user.password) click_button("Sign In") end 

我意识到实例变量@user是一种糟糕的forms – 但我认为在登录/退出的情况下,拥有@user肯定是有帮助的。

有时我叫它@current_user

我将重复danpickett说黄瓜应该尽可能避免嘲笑。 但是,如果您的应用程序没有登录页面,或者性能可能有问题,则可能需要直接模拟登录。

这是一个丑陋的黑客,但它应该完成工作。

 Given /^I am logged in as "(.*)"$/ do |email| @current_user = Factory(:user, :email => email) cookies[:stub_user_id] = @current_user.id end # in application controller class ApplicationController < ActionController::Base if Rails.env.test? prepend_before_filter :stub_current_user def stub_current_user session[:user_id] = cookies[:stub_user_id] if cookies[:stub_user_id] end end end 

回覆。 Ryan的解决方案 – 您可以在env.rb文件中打开ActionController并将其放在那里以避免输入您的生产代码库(感谢john @ pivotal labs)

 # in features/support/env.rb class ApplicationController < ActionController::Base prepend_before_filter :stub_current_user def stub_current_user session[:user_id] = cookies[:stub_user_id] if cookies[:stub_user_id] end end 

我不知道这与原来的问题有什么关系,但我决定以讨论的精神发帖…

我们有一个黄瓜测试套件,运行时间超过10分钟,所以我们想做一些优化。 在我们的应用程序中,登录过程会触发大量与大多数场景无关的额外function,因此我们希望通过直接设置会话用户ID来跳过此function。

Ryanb的上述方法效果很好,除了我们无法使用该方法注销。 这使我们的多用户故事失败了。

我们最终创建了一个仅在测试环境中启用的“快速登录”路由:

 # in routes.rb map.connect '/quick_login/:login', :controller => 'logins', :action => 'quick_login' 

以下是创建会话变量的相应操作:

 # in logins_controller.rb class LoginsController < ApplicationController # This is a utility method for selenium/webrat tests to speed up & simplify the process of logging in. # Please never make this method usable in production/staging environments. def quick_login raise "quick login only works in cucumber environment! it's meant for acceptance tests only" unless Rails.env.test? u = User.find_by_login(params[:login]) if u session[:user_id] = u.id render :text => "assumed identity of #{u.login}" else raise "failed to assume identity" end end end 

对我们来说,这比使用cookies数组更简单。 作为奖励,这种方法也适用于Selenium / Watir。

缺点是我们在应用程序中包含与测试相关的代码。 就个人而言,我认为添加代码以使应用程序更易于测试是一个巨大的罪恶,即使它确实增加了一些混乱。 也许最大的问题是未来的测试作者需要弄清楚他们应该使用哪种登录方式。 凭借无限的硬件性能,我们显然不会做任何这样的事情。

回复:瑞恩的解决方案:

不适用于Capybara,除非进行了小规模的改编:

 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 

(在这里找到: https : //gist.github.com/484787 )

我的理解是你得到:

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

在实例化请求之前访问session []时。 在你的情况下,我想如果你在步骤visit some_existing_path访问session []之前让webrats visit some_existing_path ,那么错误就会消失。

现在,不幸的是,会话似乎没有跨越步骤(至少,我找不到方法),所以这些信息无助于回答你的问题:)

所以,我想,Ryan的session[:user_id] = cookies[:stub_user_id]...是要走的路。 虽然,imo,应用程序本身的测试相关代码听起来不对。

我使用像Prikka这样的仅测试登录解决方案,但我在Rack中完成所有操作,而不是创建新的Controller和路由。

 # 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) 

@ Ajedi32我遇到了同样的问题(未定义的方法’current_session’用于Capybara :: RackTest :: Driver)并将其放入我的步骤定义中为我解决了问题:

 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 

在我的控制器操作中,我提到了cookies [:stub_user_id],而不是cookie_jar [:stub_user_id]

为什么不使用FactoryGirl或(Fixjour或Fabricator)与Devise(或Authlogic)和SentientUser ? 然后你可以简单地嗅探哪个用户已经登录!

 @user = Factory(:user) # FactoryGirl sign_in @user # Devise User.current.should == @user # SentientUser 

另一个微小变化:

 # 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 

经过大量的灵魂搜索和网上冲浪后,我终于选择了一个非常简单明了的解决方案。

使用cookie会增加两个问题。 首先,您在应用程序中具有特定于测试的代码,然后存在以下问题:在使用除机架测试之外的任何其他内容时,在Cucumber中创建cookie很困难。 cookie问题有各种各样的解决方案但是所有这些都有点挑战,有些引入了模拟,所有这些都是我称之为“棘手”的问题。 这里有一个这样的解决方案

我的解决方案如下。 这是使用HTTP基本身份validation,但它可以推广到大多数任何东西。

  authenticate_or_request_with_http_basic "My Authentication" do |user_name, password| if Rails.env.test? && user_name == 'testuser' test_authenticate(user_name, password) else normal_authentication end end 

test_authenticate会执行普通身份validation所做的操作,除非它绕过任何耗时的部分。 就我而言,真正的身份validation使用的是我想要避免的LDAP。

是的……它有点粗糙,但它清晰,简单,明显。 并且……我见过的其他解决方案更清晰或更清晰。

注意,一个特性是如果user_name不是’testuser’,则采用正常路径以便对它们进行测试。

希望这有助于其他人……