Как проверить ограничения маршрута с помощью rspec - программирование
Подтвердить что ты не робот

Как проверить ограничения маршрута с помощью rspec

Я работаю над приложением, которое будет в основном использоваться как API (кроме нескольких второстепенных видов, таких как сеанс/регистрация, который будет "стандартным" ). Мне нравится подход, который был завершен в Railscast # 350: Versioning API, и поэтому последовал за ним. Мои маршруты выглядят так:

namespace :api, :defaults => {:format => 'json'} do
  scope :module => :v1, :constraints => ApiConstraints.new(:version => 1, :default => false) do
    resources :posts, :only => [:create, :show, :destroy, :index]
  end

  scope :module => :v2, :constraints => ApiConstraints.new(:version => 2, :default => true) do
    resources :posts, :only => [:create, :show, :destroy, :index]
  end
end

В каждом маршруте мой Constraint - это новый объект ApiConstraints, который находится в моей папке ./lib. Класс выглядит следующим образом:

class ApiConstraints
  def initialize(options)
    @version = options[:version]
    @default = options[:default]
  end

  def matches?(req)
    @default || req.headers['Accept'].include?("application/vnd.MYAPP.v#{@version}")
  end
end

Теперь при проверке вручную все работает так, как ожидалось. В моем API у меня может быть от 5 до 10 контроллеров на версию и не хочу проверять, что ограничения API работают для каждого отдельного контроллера, поскольку это не имеет никакого смысла. Я ищу один файл спецификации, который проверяет мои ограничения API, но я не уверен, где разместить эту спецификацию.

Я попытался добавить файл spec/routing/api_spec.rb для проверки вещей, но он не работает должным образом, поскольку он жалуется, что некоторые вещи не предоставляются, например:

it "should route an unversioned request to the latest version" do
  expect(:get => "/api/posts", :format => "json").to route_to(:controller => "api/v1/posts")
end

Вышеупомянутая ошибка вызывает ошибку, даже если контроллер правильно соответствует. Он не работает со следующей ошибкой:

The recognized options <{"format"=>"json", "action"=>"index", "controller"=>"api/v1/posts"}>
did not match <{"controller"=>"api/v1/posts"}>,
difference: <{"format"=>"json", "action"=>"index"}>.

Обратите внимание, что контроллер был определен правильно, но поскольку я не хочу проверять формат и действие в этом тесте, он не работает. Я бы хотел, чтобы было 3 "спецификации API":

  • Он должен перенаправить неверсированный запрос в последнюю версию
  • Он должен по умолчанию использовать формат JSON, если ни один не указан
  • Он должен вернуть указанную версию API по запросу

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

4b9b3361

Ответ 1

Rspec route_to выполняет согласование ActionDispatch::Assertions::RoutingAssertions#assert_recognizes

Аргумент route_to передается как хэш expected_options (после некоторой предварительной обработки, которая позволяет ему также понимать аргументы стенографического стиля, такие как items#index).

Хэш, который вы ожидаете согласовать с route_to (т.е. {:get => "/api/posts", :format => "json"}), на самом деле не является корректным аргументом expect. Если вы посмотрите на источник, вы увидите, что мы получаем путь к сопоставлению с помощью

path, query = *verb_to_path_map.values.first.split('?')

#first - верный признак того, что мы ожидаем хеш с одной парой ключ-значение. Таким образом, компонент :format => "json" фактически просто отбрасывается и ничего не делает.

Утверждение ActionDispatch предполагает, что вы должны сопоставить полный путь + глагол с полным набором параметров контроллера, действия и пути. Таким образом, сопоставление rspec просто проходит по ограничениям метода, которому он делегирует.

Похоже, что встроенный route_to сокет rspec не будет делать то, что вы хотите. Поэтому следующим предложением было бы предположить, что ActionDispatch сделает то, что он должен делать, а вместо этого просто напишет спецификации для вашего класса ApiConstraints.

Для этого я бы сначала не рекомендовал использовать spec_helper по умолчанию. Corey Haines имеет приятный сущность о как сделать быстрый помощник по спецификации, который не разворачивает все приложение rails. Возможно, это не идеально подходит для вашего случая как есть, но я просто подумал, что хочу указать на это, поскольку вы просто создаете базовые объекты ruby ​​здесь и на самом деле не нуждаетесь в магии рельсов. Вы также можете попробовать ActionDispatch::Request и зависимости, если вы не хотите заглушить объект запроса, как я здесь.

Это выглядело бы как

spec/lib/api_constraint.rb

require 'active_record_spec_helper'
require_relative '../../lib/api_constraint'

describe ApiConstraint do

  describe "#matches?" do

    let(:req) { Object.new }

     context "default version" do

       before :each do
         req.stub(:headers).and_return {}
         @opts = { :version => nil, :default => true }
       end

       it "returns true regardless of version number" do
         ApiConstraint.new(@opts).should match req
       end

     end

  end

end

... aaand Я дам вам понять, как настроить контекст/написать ожидания для других тестов.