2013-04-17 4 views
16

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

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, так как они не отвечают за эту функциональность.

ответ

4

route_to Сличителя делегаты RSpec, чтобы ActionDispatch::Assertions::RoutingAssertions#assert_recognizes

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

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

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

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

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

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

Для этого я бы рекомендовал не по умолчанию spec_helper. Corey Haines имеет приятный сущность около how to make a faster spec helper that doesn't spin up the whole rails app. Возможно, это не идеально подходит для вашего случая как есть, но я просто подумал, что хочу указать на это, поскольку вы просто создаете базовые объекты 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 Я дам вам точно выяснить, как настроить контекст/запись ожидания для других тестов.

+0

Да, это правильно. В идеале, я хочу три теста в своем файле спецификации api, один для проверки того, что работает формат по умолчанию, один для проверки того, что он маршрутизируется на допустимый контроллер, когда не указана никакая версия, и чтобы проверить, что он перенаправляет правильную версию, когда версия IS. –

+1

Ну, используя 'route_to' вам нужно предоставить более конкретные ожидания, например' ожидать (: get => "/api/posts.json"').to route_to (: controller =>" api/v1/posts ",: action => "index",: format => "json") '. Там, к сожалению, нет способа обойтись со стандартными rspec-rails. – gregates

+0

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

Смежные вопросы