代码之家  ›  专栏  ›  技术社区  ›  Matthew Savage

黄瓜故事会话变量

  •  16
  • Matthew Savage  · 技术社区  · 15 年前

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

    而是写一个Huuuuuge故事,一次涵盖所有步骤,这将是 坏的 我更愿意像普通用户一样完成控制器中的每个操作。这里的问题是,我将第一步中创建的帐户ID存储为会话变量,因此当访问第2步、第3步等时,将加载现有注册数据。

    我知道可以进入 controller.session[..] 然而,在RSPEC规范中,当我尝试在黄瓜故事中这样做时,它失败了,并出现了以下错误(而且,我也在某个地方读到了这是一个反模式等)。

    使用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等,但我还没有找到我的问题的答案……

    为了更清楚一点,注册过程更像是一个状态机-例如,用户在注册完成之前经历了四个步骤-因此“登录”不是一个真正的选项(它打破了网站工作模式)。

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

    谢谢!

    11 回复  |  直到 11 年前
        1
  •  19
  •   Zearin lifeisstillgood    11 年前

    模拟在黄瓜场景中很糟糕-它们几乎是一种反模式。

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

    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 是一种不好的形式,但我认为在登录/注销的情况下, @用户 绝对有用。

    有时我叫它 @current_user .

        2
  •  25
  •   ryanb    15 年前

    我再重复一遍丹皮克特的话,他说只要有可能,就应该避免用黄瓜做模拟。但是,如果您的应用程序没有登录页面,或者性能有问题,那么可能需要直接模拟登录。

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

    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
    
        3
  •  17
  •   brez    15 年前

    重新。Ryan的解决方案-您可以在env.rb文件中打开actioncontroller并将其放在那里,以避免将其放入生产代码库(感谢John@Pivotal实验室)

    # 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
    
        4
  •  5
  •   Pirkka Esko    15 年前

    我不知道这和最初的问题有多大关系,但我还是决定本着讨论的精神发表…

    我们有一个黄瓜测试套件,运行需要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数组简单。作为额外的好处,这种方法也适用于硒/watir。

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

        5
  •  4
  •   atretkow    14 年前

    回复:Ryan的解决方案:

    不适用于水豚,除非进行了小的适应:

    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 )

        6
  •  3
  •   artemave    15 年前

    我的理解是你得到:

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

    在请求被实例化之前访问会话[]时。在你的情况下,我想如果你把webrats visit some_existing_path 在访问步骤定义中的会话[]之前,错误将消失。

    现在,不幸的是,会话似乎没有跨步骤持续(至少,我找不到方法),所以这一点信息没有帮助回答您的问题:)

    所以,我想,Ryan的 session[:user_id] = cookies[:stub_user_id]... 是前进的道路。尽管,在IMO中,应用程序本身的测试相关代码听起来并不正确。

        7
  •  2
  •   Community CDub    8 年前

    我使用一个只测试的登录解决方案,比如 Prikka's 但我在机架中完成了这一切,而不是创建一个新的控制器和路由。

    # 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)
    
        8
  •  2
  •   adamc    12 年前

    @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]

        9
  •  1
  •   thekingoftruth    13 年前

    你为什么不把工厂女工或(修理工或制造工)用在设计(或权威)和 SentientUser ?然后您可以简单地嗅探哪个用户已经登录!

    @user = Factory(:user)       # FactoryGirl
    sign_in @user                # Devise
    User.current.should == @user # SentientUser
    
        10
  •  0
  •   Kerry Buckley    14 年前

    另一个微小的变化:

    # 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
    
        11
  •  0
  •   pedz    12 年前

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

    使用cookie会增加两个问题。首先,您在特定于测试的应用程序中有代码,第二个问题是,在使用机架测试以外的任何东西时,在Cucumber中创建cookie都很困难。对于cookie问题,有各种各样的解决方案,但是所有的都有点挑战性,一些介绍了mock,所有的都是我所说的“棘手的”。一个这样的解决方案是 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做任何正常的authenticate所做的事情,除了绕过任何耗时的部分。在我的例子中,真正的身份验证是使用LDAP,我想避免使用LDAP。

    是的,它有点粗俗,但它是清晰、简单和明显的。我看到的其他解决方案都不是更干净更清晰。

    注意,一个特性是,如果用户名不是“testuser”,则采用常规路径,以便对其进行测试。

    希望这能帮助别人…