Как проверить ограничения маршрута с помощью 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, так как они не отвечают за эту функциональность.
Ответы
Ответ 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 Я дам вам понять, как настроить контекст/написать ожидания для других тестов.