2013-04-17 14 views
16

Pracuję nad aplikacją, która będzie głównie obsługiwana jako API (inne niż kilka mniejszych widoków, takich jak sesja/rejestracja, które będą "standardowe"). Podoba mi się podejście, które zostało sfinalizowane w Railscast #350: Versioning an API, i tak po nim. Moje trasy wyglądać następująco:Jak przetestować ograniczenia trasy za pomocą 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 

W każdej trasie, moja ograniczeniem jest nowy ApiConstraints obiekt, który znajduje się w folderze Moje ./lib. Klasa wygląda następująco:

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 

Teraz, podczas testowania ręcznego, wszystko działa zgodnie z oczekiwaniami. W moim API mogę mieć od 5 do 10 kontrolerów na wersję i nie chcę sprawdzać, czy ograniczenia API działają dla każdego pojedynczego kontrolera, ponieważ to nie ma sensu. Szukam jednego pliku spec, który testuje moje ograniczenia API, ale nie jestem pewien, gdzie umieścić tę specyfikację.

Próbowałem dodanie pliku spec/routing/api_spec.rb przetestować rzeczy, ale to nie działa prawidłowo, ponieważ twierdzi, że niektóre rzeczy nie są, tak jak poniżej:

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 

Powyższy zgłasza błąd, chociaż kontroler pasuje poprawnie. Nie powiedzie się z powodu następującego błędu:

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

Zauważ, że sterownik został prawidłowo ustalone, ale ponieważ nie chcę, aby przetestować dla formatu i działania w tym teście, to błędy na zewnątrz. Chciałbym tam być 3 „specyfikacje API”:

  • Powinno trasa niewersjonowany prośba do najnowszej wersji
  • Należy domyślnych do formatu JSON, jeśli żadna określona
  • Należy zwrócić określony Wersja API na żądanie

Czy ktoś ma doświadczenie w pisaniu specyfikacji dla tego rodzaju tras? Nie chcę dodawać specyfikacji dla każdego kontrolera w interfejsie API, ponieważ nie są odpowiedzialni za tę funkcjonalność.

Odpowiedz

4

rspec za route_to Matcher delegaci ActionDispatch::Assertions::RoutingAssertions#assert_recognizes

argument do route_to jest przekazywana jako expected_options hash (po pewnym wstępnego przetwarzania, który umożliwia mu także zrozumieć argumenty skrótowym stylu jak items#index).

Wartość mieszania, która ma być dopasowana do macierzy route_to (tj. {:get => "/api/posts", :format => "json"}), w rzeczywistości nie jest dobrze sformułowanym argumentem dla expect. Jeśli spojrzeć na the source, można zobaczyć, że mamy ścieżka dopasować przeciwko poprzez

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

#first jest to pewny znak, że jesteśmy oczekując hash z tylko jednej pary klucz-wartość. Tak więc komponent :format => "json" jest po prostu odrzucany i nic nie robi.

Twierdzenie ActionDispatch oczekuje, że dopasujesz pełną ścieżkę + czasownik do pełnego zestawu parametrów kontrolera, działania, & ścieżki.Tak więc rspec matcher po prostu przekazuje ograniczenia metody, którą deleguje.

Brzmi jak rspec wbudowanej route_to dopasowującego nie będą robić to, co chcemy. Więc następnym sugestia byłoby zakładać ActionDispatch zrobi to, co ma robić, i zamiast po prostu napisać specyfikacje dla swojej klasy ApiConstraints.

Aby to zrobić, najpierw polecam , a nie przy użyciu domyślnego spec_helper. Corey Haines ma fajny sens o how to make a faster spec helper that doesn't spin up the whole rails app. Być może nie jest to idealne rozwiązanie dla twojego przypadku, ale pomyślałem, że wskażę to, ponieważ właśnie tworzysz tutaj podstawowe obiekty ruby ​​i naprawdę nie potrzebujesz magii szyn. Możesz także spróbować wymagać zależności od ActionDispatch::Request &, jeśli nie chcesz wyodrębnić obiektu żądania, tak jak robię to tutaj.

To by wyglądać jak

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 dam Ci dowiedzieć się dokładnie, jak skonfigurować kontekst/zapisu oczekiwania w stosunku do drugiego badania.

+0

Tak, to prawda. Idealnie, chcę trzech testów w moim pliku specyfikacji api, jeden do sprawdzenia, czy działa domyślny format, jeden do sprawdzenia, czy kieruje się do poprawnego kontrolera, gdy nie podano żadnej wersji, i do sprawdzenia, czy kieruje się do właściwej wersji, gdy Wersja IS została określona. –

+1

Cóż, używając 'route_to' trzeba zapewnić bardziej konkretne oczekiwania, jak' spodziewać (: get => „/api/posts.json"').to route_to (: controller => "API/v1/posty": action => "index",: format => "json") '. Niestety nie da się tego obejść za pomocą domyślnych dopasowań rspec-rails. – gregates

+0

Problem polega na tym, że każda specyfikacja testuje logikę z każdej innej specyfikacji. Zasadniczo przetwarza wszystkie specyfikacje w jeden test, co nie jest idealne. –

Powiązane problemy