Stubbing-Authentifizierung in Anforderungsspezifikation


84

Wie legen Sie beim Schreiben einer Anforderungsspezifikation Sitzungen und / oder Stub-Controller-Methoden fest? Ich versuche, die Authentifizierung in meinen Integrationstests zu unterbinden - rspec / request

Hier ist ein Beispiel für einen 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

Der Helfer:

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

Und der ApplicationController, der die Authentifizierung übernimmt:

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

Warum ist es nicht möglich, auf diese Ressourcen zuzugreifen?

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

Antworten:


101

Eine Anforderungsspezifikation ist ein Thin Wrapper ActionDispatch::IntegrationTest, der nicht wie Controller-Spezifikationen (welche Wraps ActionController::TestCase) funktioniert . Obwohl eine Sitzungsmethode verfügbar ist, glaube ich nicht, dass sie unterstützt wird (dh sie ist wahrscheinlich vorhanden, weil ein Modul, das für andere Dienstprogramme enthalten ist, auch diese Methode enthält).

Ich würde empfehlen, sich anzumelden, indem Sie eine Aktion veröffentlichen, mit der Sie Benutzer authentifizieren. Wenn Sie das Kennwort "Kennwort" (zum Beispiel) für alle Benutzerfabriken festlegen, können Sie Folgendes tun:

def login (Benutzer)
  post login_path ,: login => user.login ,: password => 'password'
Ende

1
Danke David. Es funktioniert großartig, aber es scheint ein wenig übertrieben zu sein, all diese Anfragen zu stellen?
Jonas Nielsen

19
Wenn ich gedacht hätte, es wäre übertrieben, hätte ich es nicht empfohlen :)
David Chelimsky

5
Dies ist auch der einfachste Weg, dies zuverlässig zu tun. ActionDispatch::IntegrationTestwurde entwickelt, um einen oder mehrere Benutzer zu simulieren, die über Browser interagieren, ohne echte Browser verwenden zu müssen. In einem Beispiel gibt es möglicherweise mehr als einen Benutzer (dh eine Sitzung) und mehr als einen Controller, und die Sitzungs- / Controller-Objekte sind diejenigen, die in der letzten Anforderung verwendet wurden. Sie haben vor einer Anfrage keinen Zugriff darauf.
David Chelimsky

17
Ich muss page.driver.postmit Capybara
Ian Yang

@ IanYang page.driver.postkann ein Antimuster sein, laut Jonas Nicklas in Capybara und Test-APIs )
Epigene

61

Hinweis für Gerätebenutzer ...

Übrigens, die Antwort von @David Chelimsky muss möglicherweise etwas angepasst werden, wenn Sie Devise verwenden . Was ich in meinen Integrations- / Anforderungstests mache (dank dieses StackOverflow-Beitrags ):

# file: spec/requests_helper.rb
def login(user)
  post_via_redirect user_session_path, 'user[email]' => user.email, 'user[password]' => user.password
end

2
Wenn ich dann 'login user1' in einer rspec-Modellspezifikation verwende, erhalte ich eine undefinierte lokale Variable oder Methode 'user_session_path' für # <RSpec :: Core:
jpw

1
Dies setzt voraus, dass Sie devise_for :usersin der config/routes.rbDatei haben. Wenn Sie etwas anderes angegeben haben, müssen Sie Ihren Code entsprechend anpassen.
fearless_fool

Das hat bei mir funktioniert, aber ich musste es leicht modifizieren. Ich habe zu geändert 'user[email]' => user.email, 'user[username]' => user.usernameda meine App den Benutzernamen als Login anstelle von E-Mail verwendet.
Webdevguy

3

FWIW, als ich meine Test :: Unit-Tests auf RSpec portierte, wollte ich mich in meinen Anforderungsspezifikationen mit mehreren (Entwurfs-) Sitzungen anmelden können. Es hat ein bisschen gegraben, aber das hat für mich funktioniert. Verwenden von Rails 3.2.13 und 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

Und...

# 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

Bearbeiten : Tippfehler behoben


-1

Sie könnten die Sitzung auch ziemlich leicht abbrechen.

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

Alle Ruby-Spezialoperatoren sind in der Tat Methoden. Das Aufrufen 1+1ist dasselbe wie 1.+(1), was bedeutet, dass +es nur eine Methode ist. Ebenso session[:user_id]ist das gleiche wie das Aufrufen von method []on session, assession.[](:user_id)


Dies scheint eine vernünftige Lösung zu sein.
Superluminary

2
Dies funktioniert nicht in einer Anforderungsspezifikation, sondern nur in einer Controller-Spezifikation.
Machisuji

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.