Kończenie uwierzytelniania w specyfikacji żądania

84

Pisząc specyfikację żądania, w jaki sposób ustawiasz sesje i / lub metody kontrolera pośredniczącego? Próbuję zablokować uwierzytelnianie w moich testach integracji - rspec / requests

Oto przykład testu

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

Pomocnik:

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

Oraz ApplicationController, który obsługuje uwierzytelnianie:

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

Dlaczego nie można uzyskać dostępu do tych zasobów?

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
źródło

Odpowiedzi:

101

Specyfikacja żądania jest cienkim opakowaniem ActionDispatch::IntegrationTest, które nie działa jak specyfikacje kontrolera (które zawijają ActionController::TestCase). Mimo że jest dostępna metoda sesji, nie sądzę, aby była obsługiwana (tj. Prawdopodobnie jest dostępna, ponieważ moduł dołączany do innych narzędzi zawiera również tę metodę).

Zalecam zalogowanie się, wysyłając wiadomość do dowolnej akcji, której używasz do uwierzytelniania użytkowników. Jeśli utworzysz hasło „hasło” (na przykład) dla wszystkich fabryk użytkowników, możesz zrobić coś takiego:

def login (użytkownik)
  post login_path,: login => user.login,: password => 'hasło'
koniec
David Chelimsky
źródło
1
Dzięki David. Działa świetnie, ale czy wszystkie te prośby wydają się przesadą?
Jonas Nielsen
19
Gdybym pomyślał, że to przesada, nie poleciłbym tego :)
David Chelimsky
6
To także najprostszy sposób, aby zrobić to niezawodnie. ActionDispatch::IntegrationTestjest przeznaczony do symulacji interakcji jednego lub większej liczby użytkowników za pośrednictwem przeglądarek bez konieczności korzystania z prawdziwych przeglądarek. W jednym przykładzie istnieje potencjalnie więcej niż jeden użytkownik (tj. Sesja) i więcej niż jeden kontroler, a obiekty sesji / kontrolera są tymi, które zostały użyte w ostatnim żądaniu. Nie masz do nich dostępu przed złożeniem wniosku.
David Chelimsky
17
Muszę używać page.driver.postz Kapibarą
Ian Yang
@IanYang page.driver.postmoże być anty-wzorcem, według Jonasa Nicklasa z Kapibary i testujących API )
Epigene,
61

Uwaga dla użytkowników Devise ...

BTW, odpowiedź @David Chelimsky może wymagać drobnych poprawek, jeśli używasz Devise . Co robię podczas testów integracji / żądań (dzięki temu postowi StackOverflow ):

# file: spec/requests_helper.rb
def login(user)
  post_via_redirect user_session_path, 'user[email]' => user.email, 'user[password]' => user.password
end
nieustraszony głupiec
źródło
2
kiedy używam 'login user1' w specyfikacji modelu rspec, otrzymuję niezdefiniowaną zmienną lokalną lub metodę 'user_session_path' dla # <RSpec :: Core:
jpw
1
Zakłada się, że masz devise_for :usersw config/routes.rbaktach. Jeśli podałeś coś innego, będziesz musiał odpowiednio dostosować kod.
Fearless_fool
U mnie to zadziałało, ale musiałem go nieco zmodyfikować. Zmieniłem 'user[email]' => user.emailna, 'user[username]' => user.usernameponieważ moja aplikacja używa nazwy użytkownika jako loginu zamiast adresu e-mail.
webdevguy
3

FWIW, przenosząc moje Test :: Unit tests na RSpec, chciałem mieć możliwość logowania się z wieloma sesjami (devise) w specyfikacji mojego żądania. Wymagało to trochę kopania, ale udało mi się to. Korzystanie z Rails 3.2.13 i 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

I...

# 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

Edycja : poprawiona literówka

turboladen
źródło
-1

Możesz również dość łatwo zgasić sesję.

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

Wszystkie operatory specjalne ruby ​​są rzeczywiście metodami. Wywołanie 1+1jest tym samym 1.+(1), co, co oznacza, że +jest to tylko metoda. Podobnie session[:user_id]działa tak samo jak wywołanie metody []on session, assession.[](:user_id)

Subhas
źródło
Wydaje się, że to rozsądne rozwiązanie.
superluminium
2
Nie działa to w specyfikacji żądania, ale tylko w specyfikacji kontrolera.
Machisuji