Подтвердить что ты не робот

Установить заголовок в запросе RSpec 3

Я пытаюсь установить заголовок для некоторых запросов RSpec, требующих проверки подлинности. Заголовок ACCESS_TOKEN. Независимо от того, как я пытаюсь установить заголовок, он никогда не будет установлен. Я знаю, что приложение работает, потому что я могу его вручную протестировать, я просто не могу запустить тесты rspec. Полный исходный код и тесты для этой проблемы см. Здесь: https://github.com/lightswitch05/rspec-set-header-example

Так как аутентификация используется в большинстве моих спецификаций запросов, я создал вспомогательный модуль поддержки для получения токена доступа и установки его в заголовке. Ниже приведено краткое описание того, как я пытаюсь установить заголовок, увидеть все, что я пробовал в полном источнике

# my_app/spec/support/session_helper.rb
module SessionHelper
  def retrieve_access_token
    post api_v1_session_path({email: '[email protected]', password: 'poor_password'})

    expect(response.response_code).to eq 201
    expect(response.body).to match(/"access_token":".{20}"/)
    parsed = JSON(response.body)
    token = parsed['access_token']['access_token']

    @request.headers['HTTP_ACCESS_TOKEN'] = token
  end
end

пример запроса spec, который использует этот помощник и должен работать, но всегда терпит неудачу, потому что заголовок никогда не устанавливается:

# my_app/spec/requests/posts_spec.rb
# ...
context "create" do
  it "creates a post" do
    retrieve_access_token
    post = FactoryGirl.build(:post)

    post api_v1_posts_path(
      post: {
        title: post.title,
        content: post.content
      }
    )

    expect(response.body).to include('"id":')
    expect(response.body).to include('"title":"' + post.title + '"')
    expect(response.body).to include('"content":"' + post.content + '"')
    expect(response.response_code).to eq 201
  end
end

Я знаю, что могу вручную настроить заголовок в отдельных запросах get и post, но это не поддерживаемое решение для авторизации по API. Представьте, что вам нужно изменить каждый тест, если имя заголовка слегка изменилось.

4b9b3361

Ответ 1

Примечание. Этот ответ основан на том, что вы вызываете api_v1_session_path с post запросом на SessionsController для каждой спецификации, которую вы пытаетесь запустить в своих спецификациях запросов.

Есть два способа решить проблему, я полагал, что вы здесь.

Решение № 1. Создайте другой вспомогательный метод в SessionHelper или в другом вспомогательном файле под названием support/requests_helper.rb(однако вы предпочитаете). Я создам еще один помощник в support/requests_helper.rb:

module RequestsHelper
  def get_with_token(path, params={}, headers={})
    headers.merge!('HTTP_ACCESS_TOKEN' => retrieve_access_token)
    get path, params, headers
  end

  def post_with_token(path, params={}, headers={})
    headers.merge!('HTTP_ACCESS_TOKEN' => retrieve_access_token)
    post path, params, headers
  end

  # similarly for xhr..
end

затем в rails_helper.rb:

  # Include the sessions helper
  config.include SessionHelper, type: :request
  # Include the requests helper
  config.include RequestsHelper, type: :request

change session_helper.rb:

# my_app/spec/support/session_helper.rb
module SessionHelper
  def retrieve_access_token
    post api_v1_session_path({email: '[email protected]', password: 'poor_password'})

    expect(response.response_code).to eq 201
    expect(response.body).to match(/"access_token":".{20}"/)
    parsed = JSON(response.body)
    parsed['access_token']['access_token'] # return token here!!
  end
end

Теперь вы можете изменить все ваши запросы, например:

describe Api::V1::PostsController do

  context "index" do
    it "retrieves the posts" do
      get_with_token api_v1_posts_path

      expect(response.body).to include('"posts":[]')
      expect(response.response_code).to eq 200
    end

    it "requires a valid session key" do
      get api_v1_posts_path

      expect(response.body).to include('"error":"unauthenticated"')
      expect(response.response_code).to eq 401
    end
  end
end

Решение №2 - изменить спецификации/фабрики/access_token_factory.rb на:

FactoryGirl.define do
  factory :access_token do
    active true
  end

  # can be used when you want to test against expired access tokens:
  factory :inactive_access_token do
    active false
  end
end

Теперь измените спецификации всех запросов на использование access_token:

describe Api::V1::PostsController do

  context "index" do
    let(:access_token){ FactoryGirl.create(:access_token) }

    it "retrieves the posts" do
      # You will have to send HEADERS while making request like this:
      get api_v1_posts_path, nil, { 'HTTP_ACCESS_TOKEN' => access_token.access_token }

      expect(response.body).to include('"posts":[]')
      expect(response.response_code).to eq 200
    end

    it "requires a valid session key" do
      get api_v1_posts_path

      expect(response.body).to include('"error":"unauthenticated"')
      expect(response.response_code).to eq 401
    end
  end
end

Я бы выбрал " Решение № 1", так как он избавляет вас от необходимости забывать отправлять HTTP_ACCESS_TOKEN в заголовках каждый раз, когда вы хотите сделать такие запросы.

Ответ 2

Распространенное заблуждение заключается в том, чтобы одинаково обрабатывать контроллер и запрашивать тесты.

Было бы неплохо начать с чтения о спецификации контроллера и спецификаций запросов. Как вы можете видеть, спецификации контроллера моделируют HTTP-запрос, тогда как спецификации запросов выполняют полный запрос стека.

Вы можете найти хорошую статью о том, почему вы должны писать спецификации контроллера и что тестировать там здесь. Хотя хорошо их писать, они, по моему мнению, не должны касаться базы данных.

Итак, пока ответ Voxdei является частично действительным (после изменения спецификаций запросов к спецификациям контроллера ваш способ настройки заголовков будет работать), это не соответствует моему мнению.

В спецификациях запросов вы не можете просто использовать методы request/controller, вам нужно передать свои заголовки в хэш как третий аргумент ваших методов запроса, так что i.e.

post '/something', {}, {'MY-HEADER' => 'value'}

То, что вы могли бы сделать, это заглушить аутентификацию, например:

before do
  allow(AccessToken).to receive("authenticate").and_return(true)
end

Затем вы можете проверить свою аутентификацию в одном spec, чтобы убедиться, что она работает, и использовать ее до фильтрации в других спецификациях. Это также, вероятно, лучший подход, поскольку выполнение дополнительного запроса каждый раз, когда вы запускаете спецификацию, требующую аутентификации, - это довольно большие издержки.

Я также нашел довольно интересный запрос на растяжение в виноградном камне, который пытается добавить поведение заголовков по умолчанию, чтобы вы могли также попробовать с таким подходом, если вы действительно хотели бы использовать заголовки по умолчанию в спецификациях запросов.

Ответ 3

Вероятно, из-за того, как теперь Rspec обрабатывает файлы спецификаций. Это больше не автоматически выводит тип спецификаций из местоположения файла

Попробуйте либо установить это поведение на то, что вы знали.

RSpec.configure do |config|
  config.infer_spec_type_from_file_location!
end

или установить его локально для каждого файла спецификации контроллера в вашем проекте

describe MyController, type: :controller do
  # your specs accessing @request
end

Ответ 4

Ответ Сурьи - лучший. Но вы можете сушить его немного больше:

def request_with_user_session(method, path, params={}, headers={})
    headers.merge!('HTTP_ACCESS_TOKEN' => retrieve_access_token)
    send(method, path, params, headers)
end

Здесь у вас есть только один метод и вызов метода запроса по заданному параметру method.

Ответ 5

Я блокирую функцию, которая аутентифицирует запрос для возврата true или любого значения, возвращаемого функцией.

ApplicationController.any_instance.stub(:authenticate_request) { true }