2013-04-17 22 views
16

Ich arbeite an einer Anwendung, die in erster Linie als API serviert wird (abgesehen von einigen kleineren Ansichten, wie Session/Registrierung, die "Standard" sein wird). Ich mag den Ansatz, der in Railscast #350: Versioning an API finalisiert wurde, und folgte ihm so. Meine Strecken wie folgt aussehen:So testen Sie Routenbeschränkungen mit 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 jeder Strecke, mein Constraint ist ein neues ApiConstraints Objekt, das in meinem ./lib-Ordner befindet. Die Klasse sieht so aus:

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 

Jetzt, wenn Sie manuell testen, funktioniert alles wie erwartet. In meiner API habe ich möglicherweise zwischen 5 und 10 Controller pro Version und möchte nicht testen, dass die API-Einschränkungen für jeden einzelnen Controller funktionieren, da dies keinen Sinn ergibt. Ich suche nach einer Spezifikationsdatei, die meine API-Einschränkungen testet, aber ich bin mir nicht sicher, wo ich diese Spezifikation platzieren soll.

Ich habe versucht, das Hinzufügen einer spec/routing/api_spec.rb Datei Dinge zu testen, aber es ist nicht richtig funktioniert, wie es beklagt, dass einige Dinge nicht vorgesehen sind, etwa so:

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 

Die oben einen Fehler wirft, obwohl Der Controller stimmt überein. Es schlägt mit dem folgenden Fehler:

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

Beachten Sie, dass der Controller korrekt bestimmt wurde, aber da will ich nicht für das Format und Wirkung in diesem Test testen, es Fehler aus. Ich möchte es 3 „API-Spezifikationen“ sein:

  • Es sollte Route eine unversionierte Anfrage auf die neueste Version
  • Auf dem JSON-Format standardmäßig sollte, wenn keine angegeben ist
  • Es sollte eine spezifizierte Rück API Version wenn angefordert

Hat jemand Erfahrung mit dem Schreiben von Spezifikationen für diese Art von Routen? Ich möchte keine Spezifikationen für jeden Controller innerhalb der API hinzufügen, da sie für diese Funktionalität nicht verantwortlich sind.

Antwort

4

Rspec des route_to Matcher Delegierten ActionDispatch::Assertions::RoutingAssertions#assert_recognizes

Das Argument zu route_to als expected_options Hash übergeben (nach einiger Vorverarbeitung, die es auch ermöglicht, Stenographie-Stil Argumente wie items#index zu verstehen).

Der Hash, den Sie voraussichtlich mit dem route_to Matcher (d. H.) übereinstimmen, ist eigentlich kein wohlgeformtes Argument für expect. Wenn man sich the source anschaut, kann man sehen, dass wir den Weg gegen übereinstimmen erhalten über

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

Die #first ist ein sicheres Zeichen dafür, dass wir einen Hash mit nur einem Schlüssel-Wert-Paar sind zu erwarten. So wird die :format => "json" Komponente gerade verworfen und tut nichts.

Die Assertion ActionDispatch erwartet, dass Sie einen vollständigen Pfad + Verb mit einem vollständigen Satz von Controller-, Aktions-, & Pfadparametern übereinstimmen.Der rspec-Matcher führt also nur die Einschränkungen der Methode weiter, an die er delegiert wurde.

Es klingt wie rspec eingebauten route_to Matcher wird nicht tun, was Sie wollen. Der nächste Vorschlag wäre also anzunehmen, dass ActionDispatch tun wird, was es tun soll, und stattdessen einfach Spezifikationen für Ihre ApiConstraints Klasse schreiben.

Um das zu tun, würde ich zuerst nicht mit dem Standard spec_helper empfehlen. Corey Haines hat einen schönen Kern über how to make a faster spec helper that doesn't spin up the whole rails app. Es ist vielleicht nicht perfekt für deinen Fall, wie es ist, aber ich dachte nur, dass ich darauf hinweisen würde, da du hier nur Rubin-Objekte instanziierst und keine Schienenmagie brauchst. Sie könnten auch versuchen, ActionDispatch::Request & Abhängigkeiten zu erfordern, wenn Sie das Anforderungsobjekt, wie ich hier nicht auszudrücken möchten.

dass so etwas wie

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 

aussehen würde ... aaand Ich werde Sie genau ausrechnen lassen, wie der Kontext einzurichten/die Erwartungen für die anderen Tests schreiben.

+0

Ja, das ist richtig. Idealerweise möchte ich drei Tests in meiner API-Spezifikationsdatei, eine zum Testen des Standardformats, eine zum Testen, dass sie zu einem gültigen Controller routet, wenn keine Version angegeben ist, und eine zum Testen, dass sie zur richtigen Version routet, wenn a Version ist angegeben. –

+1

Nun, mit 'route_to' müssen Sie spezifischere Erwartungen wie' expect (: get => "/api/posts.json"').route_to (: controller =>" api/v1/posts ",: action => "index",: format => "json") '. Mit den standardmäßigen rspec-rails-Matchern ist das leider nicht der Fall. – gregates

+0

Das Problem dabei ist, dass jede Spezifikation die Logik von jeder anderen Spezifikation testen wird. Es ist im Wesentlichen alle Spezifikationen in einen Test zu rollen, der nicht ideal ist. –