2013-04-17 7 views
16

Sto lavorando a un'applicazione che verrà utilizzata principalmente come API (a parte alcune visualizzazioni minori, come sessione/registrazione, che saranno "standard"). Mi piace l'approccio che è stato finalizzato in Railscast #350: Versioning an API, quindi l'ho seguito. I miei percorsi assomigliano:Come verificare i vincoli del percorso con 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 

In ogni percorso, il mio vincolo è un nuovo ApiConstraints oggetto, che si trova nella mia cartella ./lib. La classe è simile al seguente:

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 

Ora, durante il test manuale, tutto funziona come previsto. Nella mia API, potrei avere tra 5 e 10 controllori per versione e non voglio testare che i vincoli dell'API funzionino per ogni singolo controller, poiché ciò non ha senso. Sto cercando un file spec che metta alla prova i miei vincoli API, ma non sono sicuro di dove mettere le specifiche.

Ho provato ad aggiungere un file spec/routing/api_spec.rb per verificare le cose, ma non sta funzionando correttamente, come si lamenta che alcune cose non sono forniti, in questo modo:

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 

È possibile che questo genera un errore, anche se il controller corrisponde correttamente. Viene a mancare con il seguente errore:

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

Si noti che il controller è stato correttamente determinato, ma dal momento che non voglio mettere alla prova per il formato e l'azione in questo test, esso errori fuori. Vorrei ci fosse 3 "specifiche API":

  • Dovrebbe percorso una richiesta sotto controllo di versione alla versione più recente
  • Va default il formato JSON, se non viene specificato
  • Dovrebbe restituire un determinato Versione API quando richiesto

Qualcuno ha esperienza con le specifiche di scrittura per questo tipo di percorsi? Non voglio aggiungere specifiche per ogni controller all'interno dell'API, in quanto non sono responsabili di questa funzionalità.

risposta

4

di RSpec route_to matcher delegati al ActionDispatch::Assertions::RoutingAssertions#assert_recognizes

L'argomento per route_to viene passato come il expected_options hash (dopo un po 'di pre-elaborazione che gli consente di comprendere le argomentazioni stenografia in stile simile items#index).

Il l'hash che vi aspettate per abbinare il route_to matcher (vale a dire, {:get => "/api/posts", :format => "json"}) non è in realtà un argomento ben formato a expect. Se si guarda a the source, si può vedere che si ottiene il percorso per abbinare contro tramite

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

Il #first è un segno sicuro che ci aspettiamo un hash con una sola coppia chiave-valore. Quindi il componente :format => "json" viene effettivamente scartato e non sta facendo nulla.

L'asserzione ActionDispatch prevede che si corrisponda a un percorso completo + verbo a un set completo di controller, azione, parametri di percorso &.Quindi il rspec matcher sta solo superando le limitazioni del metodo a cui è delegato.

Sembra che il matcher integrato di rspec route_to non esegua ciò che si desidera. Quindi il prossimo suggerimento sarebbe quello di assumere che ActionDispatch farà quello che dovrebbe fare, e invece basta scrivere le specifiche per la tua classe .

Per fare ciò, prima raccomanderei non utilizzando il valore predefinito spec_helper. Corey Haines ha un bel succo circa how to make a faster spec helper that doesn't spin up the whole rails app. Potrebbe non essere perfetto per il tuo caso così com'è, ma ho pensato di farcelo notare dato che stai solo istanziando oggetti di rubino di base qui e non hai davvero bisogno di magie di rotaie. Si potrebbe anche provare a richiedere le dipendenze ActionDispatch::Request & se non si desidera eliminare l'oggetto richiesta come faccio qui.

Che sarebbe simile

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 Ti farò capire esattamente come impostare il contesto/scrivere le aspettative per gli altri test.

+0

Sì, questo è corretto. Idealmente, voglio tre test nel mio file spec api, uno per verificare che funzioni il formato predefinito, uno per verificare che instradi a un controller valido quando non è specificata alcuna versione e uno per verificare che instradi alla versione corretta quando versione IS specificata. –

+1

Bene, usando 'route_to' è necessario fornire aspettative più specifiche, come' expect (: get => "/api/posts.json"').to route_to (: controller =>" api/v1/posts ",: action => "index",: format => "json") '. Sfortunatamente non c'è modo di aggirarlo con gli abbinamenti rspec-rails predefiniti. – gregates

+0

Il problema è che ogni specifica testerà la logica da ogni altra specifica. Sta essenzialmente facendo rotolare tutte le specifiche in un test, il che non è l'ideale. –

Problemi correlati