Заглушка аутентификации в спецификации запроса

84

При написании спецификации запроса, как вы устанавливаете сеансы и / или методы контроллера заглушки? Я пытаюсь отключить аутентификацию в своих интеграционных тестах - rspec / requests

Вот пример теста

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

Помощник:

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

И ApplicationController, который обрабатывает аутентификацию:

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

Почему нет доступа к этим ресурсам?

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)>'
Йонас Нильсен
источник

Ответы:

101

ActionDispatch::IntegrationTestСпецификация запроса - это тонкая оболочка , которая не работает, как спецификации контроллера (которые обертывают ActionController::TestCase). Несмотря на то, что есть доступный метод сеанса, я не думаю, что он поддерживается (то есть, вероятно, он существует, потому что модуль, который включается для других утилит, также включает этот метод).

Я бы рекомендовал войти в систему, разместив сообщение о любом действии, которое вы используете для аутентификации пользователей. Если вы сделаете пароль 'password' (например) для всех пользовательских фабрик, вы можете сделать что-то вроде этого:

def логин (пользователь)
  post login_path,: login => user.login,: password => 'пароль'
конец
Давид Челимский
источник
1
Спасибо, Дэвид. Он отлично работает, но кажется излишним делать все эти запросы?
Йонас Нильсен,
19
Если бы я думал, что это перебор, я бы не рекомендовал это :)
Давид Челимский
6
Это также самый простой способ сделать это надежно. ActionDispatch::IntegrationTestпредназначен для имитации взаимодействия одного или нескольких пользователей через браузеры без необходимости использования реальных браузеров. В одном примере потенциально может быть более одного пользователя (т.е. сеанса) и более одного контроллера, а объекты сеанса / контроллера - это те, которые использовались в последнем запросе. У вас нет доступа к ним до запроса.
Давид Челимский
17
Я должен использовать page.driver.postс Capybara
Ян Ян
@IanYang page.driver.postможет быть антипаттерном, согласно Джонасу Никласу в Capybara and testing APIs )
Эпиген
61

Примечание для пользователей Devise ...

Кстати, ответ @David Chelimsky может потребовать небольшой настройки, если вы используете Devise . Что я делаю при тестировании интеграции / запросов (благодаря этому сообщению на 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
когда я затем использую 'login user1' в спецификации модели rspec, я получаю неопределенную локальную переменную или метод 'user_session_path' для # <RSpec :: Core:
jpw
1
Это предполагает , что вы имеете devise_for :usersв config/routes.rbфайле. Если вы указали что-то другое, вам придется соответствующим образом настроить свой код.
fearless_fool
Это сработало для меня, но мне пришлось немного его изменить. Я изменил 'user[email]' => user.emailна, 'user[username]' => user.usernameпоскольку мое приложение использует имя пользователя в качестве логина вместо электронной почты.
webdevguy
3

FWIW, при переносе моих тестов Test :: Unit на RSpec, я хотел иметь возможность входить в систему с несколькими (разрабатывать) сеансами в моих спецификациях запросов. Потребовалось немного покопаться, но это сработало для меня. Использование Rails 3.2.13 и 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

И...

# 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

Изменить : исправлена ​​опечатка

турболаден
источник
-1

Вы также можете довольно легко прервать сеанс.

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

Все специальные операторы ruby ​​действительно являются методами. Вызов 1+1аналогичен вызову 1.+(1), что означает, что +это просто метод. Аналогично, session[:user_id]это то же самое, что и вызов метода []on session, посколькуsession.[](:user_id)

Субхас
источник
Это кажется разумным решением.
superluminary
2
Это не работает в спецификации запроса, а только в спецификации контроллера.
Machisuji