ruby on rails - Variables de sesión con historias de pepino
ruby-on-rails testing (11)
Estoy trabajando en algunas historias de Pepino para una aplicación de "registro" que tiene varios pasos.
En lugar de escribir una historia de Huuuuuuuge para cubrir todos los pasos a la vez, lo que sería malo , prefiero trabajar en cada acción en el controlador como un usuario normal. Mi problema aquí es que estoy almacenando el ID de cuenta que se crea en el primer paso como una variable de sesión, por lo que cuando se visitan el paso 2, el paso 3, etc., se cargan los datos de registro existentes.
Soy consciente de ser capaz de acceder a controller.session[..]
dentro de las especificaciones de RSpec. Sin embargo, cuando intento hacer esto en Historias de pepinos, falla con el siguiente error (y, también he leído en alguna parte, esto es un anti-patrón etc ...):
Usando controller.session [: lo que sea] o sesión [: lo que sea]
You have a nil object when you didn''t expect it!
The error occurred while evaluating nil.session (NoMethodError)
Usando sesión (: lo que sea)
wrong number of arguments (1 for 0) (ArgumentError)
Por lo tanto, parece que la adhesión a la tienda de sesión no es realmente posible. Lo que me pregunto es si podría ser posible (y supongo que sería mejor ...):
- Burlarse de la tienda de sesión, etc.
- Tenga un método dentro del controlador y apague eso (por ejemplo,
get_registration
que asigna una variable de instancia ...)
He mirado el libro de RSpec (bueno, lo he visto) y he echado un vistazo a través de WebRat, etc., pero realmente no he encontrado una respuesta a mi problema ...
Para aclarar un poco más, el proceso de registro es más parecido a una máquina de estados; por ejemplo, el usuario avanza cuatro pasos antes de que se complete el registro. Por lo tanto, "iniciar sesión" no es realmente una opción (rompe el modelo de cómo funciona el sitio. ) ...
En mi especificación para el controlador pude apagar la llamada al método que carga el modelo basado en la var de sesión, pero no estoy seguro de si la línea ''antipattern'' también se aplica a los stubs así como a los mockes.
¡Gracias!
¿Por qué no usa FactoryGirl o (Fixjour o Fabricator) con Devise (o Authlogic) y SentientUser ? ¡Entonces simplemente puede oler qué usuario ya ha iniciado sesión!
@user = Factory(:user) # FactoryGirl
sign_in @user # Devise
User.current.should == @user # SentientUser
@ Ajedi32 Me encontré con el mismo problema (método indefinido ''current_session'' para Capybara :: RackTest :: Driver) y poner esto en mi definición de paso solucionó el problema para mí:
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
En la acción de mi controlador, me referí a las cookies [: stub_user_id], en lugar de cookie_jar [: stub_user_id]
Después de una gran búsqueda de alma y navegación web, finalmente opté por una solución muy simple y obvia.
El uso de cookies añade dos problemas. Primero, tiene un código en la aplicación específica para la prueba y, segundo, existe el problema de que crear cookies en Cucumber es difícil cuando se utiliza cualquier otra cosa que no sea una prueba en rack. Existen varias soluciones para el problema de las cookies, pero todas son un poco desafiantes, algunas introducen simulacros y todas son lo que yo llamo "engañosas". Una de esas soluciones está here .
Mi solución es la siguiente. Esto está usando la autenticación básica HTTP, pero podría generalizarse para casi cualquier cosa.
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 hace lo que hace la autenticación normal, excepto que evita las partes que consumen tiempo. En mi caso, la autenticación real es utilizar LDAP, lo que quería evitar.
Sí ... es un poco asqueroso, pero es claro, simple y obvio. Y ... ninguna otra solución que he visto es más limpia o más clara.
Tenga en cuenta que una característica es que si el nombre de usuario no es ''testuser'', entonces se toma la ruta normal para que puedan ser probados.
Espero que esto ayude a otros ...
Mi entendimiento es que obtienes:
You have a nil object when you didn''t expect it!
The error occurred while evaluating nil.session (NoMethodError)
cuando se accede a la sesión [] antes de que se haya creado una instancia de la solicitud. En su caso, me imagino que si coloca webrats visit some_existing_path
antes de acceder a la sesión [] en su definición de paso, el error desaparecerá.
Ahora, desafortunadamente, la sesión no parece persistir en los pasos (al menos, no pude encontrar el camino), por lo que esta información no ayuda a responder su pregunta :)
Entonces, supongo que la session[:user_id] = cookies[:stub_user_id]...
de Ryan session[:user_id] = cookies[:stub_user_id]...
es el camino a seguir. Aunque, imo, el código relacionado con la prueba en la aplicación en sí no suena bien.
Otra ligera variación:
# 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
Re. La solución de Ryan: puede abrir ActionController en su archivo env.rb y colocarlo allí para evitar colocar su base de código de producción (gracias a 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
Re: la solución de Ryan:
No funciona con Capibara, a menos que se realice una pequeña adaptación:
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
(se encuentra aquí: https://gist.github.com/484787 )
Repetiré danpickett diciendo que las burlas deben evitarse siempre que sea posible en Pepino. Sin embargo, si su aplicación no tiene una página de inicio de sesión, o tal vez el rendimiento es un problema, puede ser necesario simular el inicio de sesión directamente.
Este es un truco feo, pero debería hacer el trabajo.
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
Utilizo una solución de inicio de sesión solo de prueba como la de Prikka''s , pero lo hago todo en Rack en lugar de crear un nuevo Controlador y rutas.
# 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)
Ya no sé cuánto se relaciona esto con la pregunta original, pero decidí publicar de todos modos en el espíritu de la discusión ...
Tenemos una serie de pruebas de pepinos que demora más de 10 minutos, por lo que queríamos hacer una optimización. En nuestra aplicación, el proceso de inicio de sesión desencadena MUCHA funcionalidad adicional que es irrelevante para la mayoría de los escenarios, por lo que queríamos omitirla configurando el ID de usuario de la sesión directamente.
El enfoque de Ryanb anterior funcionó muy bien, excepto que no pudimos cerrar sesión con ese enfoque. Esto hizo que nuestras historias multiusuario fracasaran.
Terminamos creando una ruta de "inicio de sesión rápido" que solo está habilitada en el entorno de prueba:
# in routes.rb
map.connect ''/quick_login/:login'', :controller => ''logins'', :action => ''quick_login''
Aquí está la acción correspondiente que crea la variable de sesión:
# 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
Para nosotros esto terminó siendo más simple que trabajar con la matriz de cookies. Como beneficio adicional, este enfoque también funciona con Selenium / Watir.
El inconveniente es que estamos incluyendo un código relacionado con las pruebas en nuestra aplicación. Personalmente, no creo que agregar código para hacer que la aplicación sea más comprobable es un gran pecado, incluso si agrega un poco de desorden. Quizás el mayor problema es que los futuros autores de pruebas deben averiguar qué tipo de inicio de sesión deben usar. Con un rendimiento de hardware ilimitado, obviamente no haríamos nada de esto.
las burlas son malas en los escenarios de pepinos, son casi una especie de antipatrón.
Mi sugerencia es escribir un paso que realmente inicie la sesión de un usuario. Lo hago de esta manera
Given I am logged in as "[email protected]"
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
Me doy cuenta de que la variable de instancia @user
es un tipo de forma incorrecta, pero creo que en el caso de iniciar / cerrar sesión, tener @user
es definitivamente útil.
A veces lo llamo @current_user
.