Autenticación de stubbing en la especificación de solicitud

84

Al escribir una especificación de solicitud, ¿cómo se configuran las sesiones y / o los métodos del controlador stub? Estoy tratando de eliminar la autenticación en mis pruebas de integración - rspec / request

Aquí tienes un ejemplo de prueba

require File.dirname(__FILE__) + '/../spec_helper'
require File.dirname(__FILE__) + '/authentication_helpers'


describe "Messages" do
  include AuthenticationHelpers

  describe "GET admin/messages" do
    before(:each) do
      @current_user = Factory :super_admin
      login(@current_user)
    end

    it "displays received messages" do
      sender = Factory :jonas
      direct_message = Message.new(:sender_id => sender.id, :subject => "Message system.", :content => "content", :receiver_ids => [@current_user.id])
      direct_message.save
      get admin_messages_path
      response.body.should include(direct_message.subject) 
    end
  end
end

El ayudante:

module AuthenticationHelpers
  def login(user)
    session[:user_id] = user.id # session is nil
    #controller.stub!(:current_user).and_return(user) # controller is nil
  end
end

Y el ApplicationController que maneja la autenticación:

class ApplicationController < ActionController::Base
  protect_from_forgery

  helper_method :current_user
  helper_method :logged_in?

  protected

  def current_user  
    @current_user ||= User.find(session[:user_id]) if session[:user_id]  
  end

  def logged_in?
    !current_user.nil?
  end
end

¿Por qué no es posible acceder a estos recursos?

1) Messages GET admin/messages displays received messages
     Failure/Error: login(@current_user)
     NoMethodError:
       undefined method `session' for nil:NilClass
     # ./spec/requests/authentication_helpers.rb:3:in `login'
     # ./spec/requests/message_spec.rb:15:in `block (3 levels) in <top (required)>'
Jonas Nielsen
fuente

Respuestas:

101

Una especificación de solicitud es una envoltura delgada ActionDispatch::IntegrationTestque no funciona como las especificaciones del controlador (que se envuelven ActionController::TestCase). Aunque hay un método de sesión disponible, no creo que sea compatible (es decir, probablemente esté ahí porque un módulo que se incluye para otras utilidades también incluye ese método).

Recomiendo iniciar sesión publicando en cualquier acción que use para autenticar a los usuarios. Si crea la contraseña 'contraseña' (por ejemplo) para todas las fábricas de usuarios, puede hacer algo como esto:

def login (usuario)
  post login_path,: login => usuario.login,: contraseña => 'contraseña'
fin
David Chelimsky
fuente
1
Gracias David. Funciona muy bien, pero ¿parece un poco exagerado hacer todas esas solicitudes?
Jonas Nielsen
19
Si pensara que era exagerado, no lo habría recomendado :)
David Chelimsky
6
También es la forma más sencilla de hacerlo de forma fiable. ActionDispatch::IntegrationTestestá diseñado para simular la interacción de uno o más usuarios a través de navegadores, sin tener que utilizar navegadores reales. Hay potencialmente más de un usuario (es decir, sesión) y más de un controlador dentro de un solo ejemplo, y los objetos de sesión / controlador son los que se usaron en la última solicitud. No tiene acceso a ellos antes de una solicitud.
David Chelimsky
17
Tengo que usarlo page.driver.postcon Carpincho
Ian Yang
@IanYang page.driver.postpuede ser un antipatrón, según Jonas Nicklas en Capybara y pruebas de API )
Epigene
61

Nota para los usuarios de Devise ...

Por cierto, la respuesta de @David Chelimsky puede necesitar algunos ajustes si está usando Devise . Lo que estoy haciendo en mi prueba de integración / solicitudes (gracias a esta publicación de StackOverflow ):

# file: spec/requests_helper.rb
def login(user)
  post_via_redirect user_session_path, 'user[email]' => user.email, 'user[password]' => user.password
end
intrépido_ tonto
fuente
2
cuando luego uso 'login user1' en una especificación del modelo rspec, obtengo una variable local indefinida o método 'user_session_path' para # <RSpec :: Core:
jpw
1
Esto supone que tiene devise_for :usersen config/routes.rbarchivo. Si ha especificado algo diferente, tendrá que modificar su código en consecuencia.
fearless_fool
Esto funcionó para mí, pero tuve que modificarlo ligeramente. Cambié 'user[email]' => user.emaila 'user[username]' => user.usernameporque mi aplicación usa el nombre de usuario como inicio de sesión en lugar de correo electrónico.
webdevguy
3

FWIW, al trasladar mis pruebas Test :: Unit a RSpec, quería poder iniciar sesión con múltiples (diseñar) sesiones en mis especificaciones de solicitud. Me tomó un poco de investigación, pero logré que esto funcionara para mí. Usando Rails 3.2.13 y RSpec 2.13.0.

# file: spec/support/devise.rb
module RequestHelpers
  def login(user)
    ActionController::IntegrationTest.new(self).open_session do |sess|
      u = users(user)

      sess.post '/users/sign_in', {
        user: {
          email: u.email,
          password: 'password'
        }
      }

      sess.flash[:alert].should be_nil
      sess.flash[:notice].should == 'Signed in successfully.'
      sess.response.code.should == '302'
    end
  end
end

include RequestHelpers

Y...

# spec/request/user_flows.rb
require 'spec_helper'

describe 'User flows' do
  fixtures :users

  it 'lets a user do stuff to another user' do
    karl = login :karl
    karl.get '/users'
    karl.response.code.should eq '200'

    karl.xhr :put, "/users/#{users(:bob).id}", id: users(:bob).id,
      "#{users(:bob).id}-is-funny" => 'true'

    karl.response.code.should eq '200'
    User.find(users(:bob).id).should be_funny

    bob = login :bob
    expect { bob.get '/users' }.to_not raise_exception

    bob.response.code.should eq '200'
  end
end

Editar : error tipográfico fijo

turbocargado
fuente
-1

También podría cortar la sesión con bastante facilidad.

controller.session.stub(:[]).with(:user_id).and_return(<whatever user ID>)

Todos los operadores especiales de ruby ​​son de hecho métodos. Llamar 1+1es lo mismo que 1.+(1), lo que significa que +es solo un método. Del mismo modo, session[:user_id]es lo mismo que llamar método []de session, comosession.[](:user_id)

Subhas
fuente
Esta parece una solución razonable.
superluminario
2
Esto no funciona en una especificación de solicitud, sino solo en una especificación de controlador.
Machisuji