16

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

与其编写一个 Huuuuuuuge 故事来一次涵盖所有步骤,那样会很糟糕,我宁愿像普通用户一样完成控制器中的每个操作。我的问题是我将在第一步中创建的帐户 ID 存储为会话变量,因此当访问第 2 步、第 3 步等时,会加载现有的注册数据。

我知道能够controller.session[..]在 RSpec 规范内访问,但是当我尝试在 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 等,但我还没有真正找到我的问题的答案......

再澄清一点,注册过程更像是一个状态机——例如,用户在注册完成之前要经过四个步骤——因此“登录”并不是一个真正的选择(它打破了网站工作方式的模型)...

在我的控制器规范中,我能够取消对基于会话变量加载模型的方法的调用 - 但我不确定“反模式”行是否也适用于存根和模拟?

谢谢!

4

11 回答 11

25

我将重复 danpickett 说在 Cucumber 中应尽可能避免模拟。但是,如果您的应用没有登录页面,或者性能可能存在问题,则可能需要直接模拟登录。

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

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
于 2009-08-18T16:43:59.633 回答
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

于 2009-08-13T23:27:15.003 回答
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
于 2010-01-15T18:59:04.593 回答
5

我不知道这与原始问题有多大关系,但我还是决定本着讨论的精神发帖......

我们有一个运行时间超过 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。

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

于 2009-12-16T14:40:22.593 回答
4

回复:瑞安的解决方案:

不适用于 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 )

于 2010-12-18T07:56:49.743 回答
3

我的理解是你得到:

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 放在您的步骤定义中,错误就会消失。

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

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

于 2009-09-07T14:23:22.070 回答
2

我使用Prikka 之类的仅用于测试的登录解决方案,但我在 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)
于 2010-03-15T21:41:53.413 回答
2

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

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]

于 2012-09-05T01:51:18.177 回答
1

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

@user = Factory(:user)       # FactoryGirl
sign_in @user                # Devise
User.current.should == @user # SentientUser
于 2011-09-08T19:50:13.573 回答
0

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

使用 cookie 会增加两个问题。首先,您在应用程序中有专门用于测试的代码,其次存在一个问题,即在 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”,则采用正常路径以便对其进行测试。

希望这对其他人有帮助...

于 2013-03-11T00:26:05.107 回答
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
于 2011-01-12T10:35:38.333 回答