Autenticação de stub na especificação do pedido

84

Ao escrever uma especificação de solicitação, como você define sessões e / ou métodos de controlador stub? Estou tentando remover a autenticação em meus testes de integração - rspec / solicitações

Aqui está um exemplo de um teste

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

O ajudante:

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

E o ApplicationController que lida com a autenticação:

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 que não é possível acessar esses 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
fonte

Respostas:

101

Uma especificação de solicitação é um wrapper fino ActionDispatch::IntegrationTest, que não funciona como as especificações do controlador (que envolvem ActionController::TestCase). Mesmo que haja um método de sessão disponível, não acho que seja compatível (ou seja, provavelmente está lá porque um módulo que é incluído para outros utilitários também inclui esse método).

Eu recomendo fazer login postando em qualquer ação que você use para autenticar usuários. Se você definir a senha 'senha' (por exemplo) para todas as fábricas de usuários, poderá fazer algo assim:

def login (usuário)
  postar login_path,: login => usuário.login,: senha => 'senha'
fim
David Chelimsky
fonte
1
Obrigado David. Funciona muito bem, mas parece um pouco exagerado fazer todos esses pedidos?
Jonas Nielsen
19
Se eu achasse que era um exagero, não teria recomendado :)
David Chelimsky
6
É também a maneira mais simples de fazer isso de forma confiável. ActionDispatch::IntegrationTestfoi projetado para simular um ou mais usuários interagindo por meio de navegadores, sem a necessidade de usar navegadores reais. Há potencialmente mais de um usuário (ou seja, sessão) e mais de um controlador em um único exemplo, e os objetos de sessão / controlador são aqueles usados ​​na última solicitação. Você não tem acesso a eles antes de uma solicitação.
David Chelimsky
17
Eu tenho que usar page.driver.postcom Capivara
Ian Yang
@IanYang page.driver.postpode ser um antipadrão, de acordo com Jonas Nicklas em Capybara e testando APIs )
Epigene
61

Nota para usuários Devise ...

BTW, a resposta de @David Chelimsky pode precisar de alguns ajustes se você estiver usando o Devise . O que estou fazendo em meus testes de integração / solicitações (graças a esta postagem StackOverflow ):

# file: spec/requests_helper.rb
def login(user)
  post_via_redirect user_session_path, 'user[email]' => user.email, 'user[password]' => user.password
end
destemido
fonte
2
quando eu uso 'login user1' em uma especificação de modelo rspec, obtenho uma variável local indefinida ou método 'user_session_path' para # <RSpec :: Core:
jpw
1
Isso pressupõe que você tenha devise_for :usersem config/routes.rbarquivo. Se você especificou algo diferente, terá que ajustar seu código de acordo.
fearless_fool
Isso funcionou para mim, mas eu tive que modificá-lo ligeiramente. Mudei 'user[email]' => user.emailpara 'user[username]' => user.usernameporque meu aplicativo usa nome de usuário como login em vez de e-mail.
webdevguy
3

FWIW, ao portar meus testes Test :: Unit para RSpec, eu queria ser capaz de fazer o login com várias (desenvolver) sessões em minhas especificações de solicitação. Demorou um pouco, mas fez funcionar para mim. Usando Rails 3.2.13 e 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

E...

# 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 : erro de digitação corrigido

turboladen
fonte
-1

Você também poderia facilmente criar um esboço da sessão.

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

Todos os operadores especiais ruby ​​são de fato métodos. Chamar 1+1é o mesmo que 1.+(1), o que significa que +é apenas um método. Da mesma forma, session[:user_id]é o mesmo que chamar o método []on session, comosession.[](:user_id)

Subhas
fonte
Esta parece ser uma solução razoável.
superluminário de
2
Isso não funciona em uma especificação de solicitação, mas apenas em uma especificação de controlador.
Machisuji