ruby-on-rails ruby-on-rails-3 constraints rails-routing

ruby on rails - Cómo probar restricciones de ruta con rspec



ruby-on-rails ruby-on-rails-3 (1)

Estoy trabajando en una aplicación que se servirá principalmente como una API (aparte de algunas vistas menores, como sesión / registro, que será "estándar"). Me gusta el enfoque que se finalizó en Railscast # 350: Versiones de una API , y así lo seguí. Mis rutas parecen:

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

En cada ruta, mi Restricción es un nuevo objeto ApiConstraints, que se encuentra en mi carpeta ./lib . La clase se ve así:

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

Ahora, cuando se prueba manualmente, todo funciona como se espera. En mi API, puedo tener entre 5 y 10 controladores por versión, y no quiero probar que las restricciones API funcionen para cada controlador individual, ya que eso no tiene sentido. Estoy buscando un archivo de especificaciones que pruebe las restricciones de mi API, pero no estoy seguro de dónde colocar esa especificación.

He intentado agregar un archivo spec/routing/api_spec.rb para probar cosas, pero no funciona correctamente, ya que se queja de que algunas cosas no se han proporcionado, como por ejemplo:

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

Lo anterior arroja un error aunque el controlador coincida correctamente. Falla con el siguiente error:

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

Observe que el controlador se determinó correctamente, pero como no quiero probar el formato y la acción en esta prueba, se produce un error. Me gustaría que hubiera 3 "especificaciones API":

  • Debería encaminar una solicitud no versionada a la última versión.
  • Debería predeterminarse al formato JSON si no se especifica ninguno
  • Debe devolver una versión de API especificada cuando se solicite

¿Alguien tiene experiencia en escribir especificaciones para este tipo de rutas? No quiero agregar especificaciones para cada controlador dentro de la API, ya que no son responsables de esta funcionalidad.


Rspec''s route_to matcher delega a ActionDispatch::Assertions::RoutingAssertions#assert_recognizes

El argumento to route_to se pasa como el hash expected_options (después de un procesamiento previo que le permite comprender también los argumentos de estilo abreviado como items#index ).

El hash que esperas que coincida con el de route_to matcher (es decir, {:get => "/api/posts", :format => "json"} ,: {:get => "/api/posts", :format => "json"} ) no es realmente un argumento bien formado para expect . Si nos fijamos en la fuente , podemos ver que obtenemos la ruta con la que coincidimos a través de

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

La #first es una señal segura de que estamos esperando un hash con un solo par clave-valor. Entonces, el componente :format => "json" realidad solo se está descartando, y no está haciendo nada.

La aserción ActionDispatch espera que usted haga coincidir una ruta completa + verbo con un conjunto completo de parámetros de controlador, acción y ruta. Así que el Rspec Matcher simplemente está pasando a lo largo de las limitaciones del método al que se delega.

Suena como si el enrutador integrado de rspec no haga lo que usted desea. Entonces, la siguiente sugerencia sería asumir que ActionDispatch hará lo que se supone que debe hacer, y en lugar de eso, simplemente escriba las especificaciones para su clase de ApiConstraints .

Para hacerlo, primero recomiendo no usar el spec_helper predeterminado. Corey Haines tiene una buena idea de cómo hacer un ayudante de especificaciones más rápido que no haga girar toda la aplicación de Rails . Puede que no sea perfecto para su caso tal como está, pero pensé que lo destacaría ya que solo está creando una instancia de los objetos básicos de rubí aquí y realmente no necesita ningún riel mágico. También puedes intentar exigir ActionDispatch::Request & dependencies si no quieres eliminar el objeto de solicitud como lo hago aquí.

Eso se vería algo así como

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

... y le dejaré averiguar exactamente cómo configurar el contexto / escribir las expectativas para sus otras pruebas.