我正在制作一些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)
所以,似乎加入会话商店真的不可能。我想知道的是,是否有可能(我猜哪个会是最好的......):
get_registration
分配一个实例变量......)我查看过RSpec书(好吧,浏览过),浏览了一下WebRat等,但我还没有真正找到问题的答案...
为了澄清一点,注册过程更像是状态机 - 例如用户在注册完成之前会经历四个步骤 - 因此“登录”实际上不是一个选项(它打破了网站工作方式的模型)......
在我对控制器的规范中,我能够根据会话var加载对方法的调用 - 但我不确定'antipattern'行是否也适用于存根和模拟?
谢谢!
答案 0 :(得分:25)
我会重复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
答案 1 :(得分:19)
在黄瓜情况下嘲笑很糟糕 - 它们几乎都是反模式。
我的建议是编写一个实际记录用户的步骤。我这样做
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
是一种糟糕的形式 - 但我认为在登录/退出的情况下,@user
肯定是有帮助的。
有时我称之为@current_user
。
答案 2 :(得分:17)
重新。 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
答案 3 :(得分:5)
我不知道这与原来的问题有多大关系,但我决定以讨论的精神发帖...
我们有一个黄瓜测试套件,需要&gt;运行10分钟,所以我们想做一些优化。在我们的应用程序中,登录过程会触发大量与大多数场景无关的额外功能,因此我们希望通过直接设置会话用户ID来跳过此功能。
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。
缺点是我们在应用程序中包含与测试相关的代码。我个人认为添加代码以使应用程序更易于测试是一个巨大的罪恶,即使它确实增加了一些混乱。也许最大的问题是未来的测试作者需要弄清楚他们应该使用哪种登录方式。凭借无限的硬件性能,我们显然不会做任何这样的事情。
答案 4 :(得分:4)
Re:Ryan的解决方案:
除非进行了小幅调整,否则不适用于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)
答案 5 :(得分:3)
我的理解是你得到:
You have a nil object when you didn't expect it!
The error occurred while evaluating nil.session (NoMethodError)
在实例化请求之前访问session []时。在您的情况下,我想如果您在步骤防御中访问会话[]之前放置了webrats'visit some_existing_path
,错误就会消失。
现在,不幸的是,会话似乎没有跨越步骤(至少,我找不到方法),所以这些信息无助于回答你的问题:)
所以,我想,瑞恩的session[:user_id] = cookies[:stub_user_id]...
是要走的路。虽然,imo,应用程序本身的测试相关代码听起来不对。
答案 6 :(得分:2)
我使用仅限测试的登录解决方案,例如Prikka's,但我在Rack中完成所有操作,而不是创建新的控制器和路由。
# 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)
答案 7 :(得分:2)
@ 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]
答案 8 :(得分:1)
为什么不将FactoryGirl或(Fixjour或Fabricator)与Devise(或Authlogic)和SentientUser一起使用?然后你可以直接嗅探哪个用户已经登录了!
@user = Factory(:user) # FactoryGirl
sign_in @user # Devise
User.current.should == @user # SentientUser
答案 9 :(得分:0)
另一个微小的变化:
# 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
答案 10 :(得分:0)
经过大量的灵魂搜索和网上冲浪后,我终于选择了一个非常简单明了的解决方案。
使用cookies会增加两个问题。首先,您在应用程序中有特定于测试的代码,其次是在使用除机架测试之外的任何东西时,在Cucumber中创建cookie很困难。 Cookie问题有各种解决方案,但所有这些都有点挑战性,有些会引入模拟,所有这些都是我称之为“棘手”的问题。其中一个解决方案是here。
我的解决方案如下。这是使用HTTP基本身份验证,但它可以推广到任何东西。
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执行正常身份验证所做的事情,除了它绕过任何耗时的部分。就我而言,真正的身份验证使用的是我想要避免使用的LDAP。
是的......它有点粗糙,但它清晰,简单,明显。并且......我所见过的其他解决方案更清晰或更清晰。
请注意,一个功能是,如果user_name不是“testuser”,则会采用正常路径以便对其进行测试。
希望这有助于其他人......