Autenticazione di stubbing nelle specifiche della richiesta


84

Quando si scrive una specifica di richiesta, come si impostano le sessioni e / oi metodi dello stub controller? Sto cercando di bloccare l'autenticazione nei miei test di integrazione: rspec / richieste

Ecco un esempio di test

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

L'aiutante:

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 l'ApplicationController che gestisce l'autenticazione:

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

Perché non è possibile accedere a queste risorse?

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)>'

Risposte:


101

Una specifica di richiesta è un involucro sottile attorno ActionDispatch::IntegrationTest, che non funziona come le specifiche del controller (che avvolgono ActionController::TestCase). Anche se è disponibile un metodo di sessione, non penso che sia supportato (cioè probabilmente è lì perché un modulo che viene incluso per altre utilità include anche quel metodo).

Consiglierei di accedere pubblicando qualsiasi azione tu usi per autenticare gli utenti. Se crei la password "password" (ad esempio) per tutte le fabbriche utente, puoi fare qualcosa del genere:

def login (utente)
  post login_path,: login => user.login,: password => 'password'
fine

1
Grazie David. Funziona benissimo, ma sembra essere un po 'eccessivo fare tutte quelle richieste?
Jonas Nielsen

19
Se avessi pensato che fosse eccessivo, non l'avrei consigliato :)
David Chelimsky

6
È anche il modo più semplice per farlo in modo affidabile. ActionDispatch::IntegrationTestè progettato per simulare uno o più utenti che interagiscono tramite browser, senza dover utilizzare browser reali. Ci sono potenzialmente più di un utente (cioè sessione) e più di un controller all'interno di un singolo esempio, e gli oggetti sessione / controller sono quelli utilizzati nell'ultima richiesta. Non puoi accedervi prima di una richiesta.
David Chelimsky,

17
Devo usare page.driver.postcon Capybara
Ian Yang il

@IanYang page.driver.postpotrebbe essere un antipattern, secondo Jonas Nicklas in Capybara e testare le API )
Epigene

61

Nota per gli utenti Devise ...

A proposito, la risposta di @David Chelimsky potrebbe richiedere un piccolo ritocco se stai usando Devise . Cosa sto facendo nel mio test di integrazione / richieste (grazie a questo post StackOverflow ):

# file: spec/requests_helper.rb

# Rails 6
def login(user)
  post user_session_path, params: {
    user: {
      email: user.email, password: user.password
    }
  }
  follow_redirect!
end

# Rails 5 or older
def login(user)
  post_via_redirect user_session_path, 'user[email]' => user.email, 'user[password]' => user.password
end

2
quando poi utilizzo 'login user1' in una specifica del modello rspec, ottengo una variabile locale non definita o il metodo 'user_session_path' per # <RSpec :: Core:
jpw

1
Questo presuppone che tu abbia devise_for :usersin config/routes.rbfile. Se hai specificato qualcosa di diverso, dovrai modificare il codice di conseguenza.
fearless_fool

Questo ha funzionato per me ma ho dovuto modificarlo leggermente. Ho cambiato 'user[email]' => user.emailin 'user[username]' => user.usernamepoiché la mia app utilizza il nome utente come login invece dell'email.
webdevguy

3

FWIW, nel portare i miei Test :: Unit test su RSpec, volevo essere in grado di accedere con più sessioni (di ideazione) nelle mie specifiche di richiesta. Ci sono voluti alcuni scavi, ma ho ottenuto che funzionasse per me. Utilizzo di 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

Modifica : errore di battitura corretto


-1

Potresti anche facilmente interrompere la sessione.

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

Tutti gli operatori speciali di ruby ​​sono effettivamente metodi. La chiamata 1+1è la stessa di 1.+(1), il che significa che +è solo un metodo. Allo stesso modo, session[:user_id]è lo stesso di chiamare il metodo []su session, comesession.[](:user_id)


Questa sembra una soluzione ragionevole.
superluminario

2
Questo non funziona in una specifica di richiesta, ma solo in una specifica del controller.
Machisuji

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.