2013-04-17 34 views
16

Tôi đang làm việc trên một ứng dụng sẽ được phục vụ chủ yếu dưới dạng API (trừ một vài lượt xem nhỏ, chẳng hạn như phiên/đăng ký, sẽ là "chuẩn"). Tôi thích cách tiếp cận đã được hoàn thành trong Railscast #350: Versioning an API và do đó theo dõi nó. tuyến đường của tôi trông giống như:Cách kiểm tra ràng buộc tuyến với 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 

Trong mỗi tuyến đường, Hạn chế của tôi là một ApiConstraints mới phản đối, mà nằm ở thư mục ./lib tôi. Lớp học trông giống như sau:

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 

Bây giờ, khi kiểm tra thủ công, mọi thứ hoạt động như mong đợi. Trong API của tôi, tôi có thể có từ 5 đến 10 bộ điều khiển cho mỗi phiên bản và không muốn kiểm tra rằng các ràng buộc API hoạt động cho từng bộ điều khiển riêng lẻ, vì điều đó không có ý nghĩa gì. Tôi đang tìm một tập tin spec để kiểm tra các ràng buộc API của tôi, nhưng tôi không chắc chắn về nơi để đặt spec đó.

Tôi đã thử thêm một tập tin spec/routing/api_spec.rb để kiểm tra điều này, nhưng nó không hoạt động đúng, vì nó than phiền rằng một số điều không được cung cấp, như vậy:

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 

ở trên ném một lỗi mặc dù bộ điều khiển phù hợp đúng cách. Lỗi này không thành công với lỗi sau:

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

Lưu ý rằng bộ điều khiển đã được xác định đúng, nhưng vì tôi không muốn kiểm tra định dạng và hành động trong thử nghiệm này, lỗi này bị lỗi. Tôi muốn có được 3 "kỹ thuật API":

  • Nó nên gửi một yêu cầu không phiên bản lên phiên bản mới nhất
  • Nó nên mặc định cho định dạng JSON nếu không chỉ định
  • Nó sẽ trả về một quy định Phiên bản API khi được yêu cầu

Có ai có kinh nghiệm viết thông số kỹ thuật cho các loại tuyến đường này không? Tôi không muốn thêm thông số kỹ thuật cho mọi bộ điều khiển bên trong API vì chúng không chịu trách nhiệm về chức năng này.

Trả lời

4

RSpec của route_to khớp đại biểu ActionDispatch::Assertions::RoutingAssertions#assert_recognizes

Các đối số để route_to được thông qua tại làm expected_options băm (sau khi một số tiền xử lý cho phép nó cũng hiểu lý lẽ viết tắt kiểu như items#index).

Giá trị băm bạn mong muốn khớp với đối sánh route_to (tức là, {:get => "/api/posts", :format => "json"}) không thực sự là đối số được định dạng tốt cho expect. Nếu bạn nhìn vào the source, bạn có thể thấy rằng chúng tôi có được con đường để phù hợp chống lại qua

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

Các #first là một dấu hiệu chắc chắn rằng chúng tôi đang mong đợi một hash chỉ với một cặp khóa-giá trị. Vì vậy, các thành phần :format => "json" thực sự chỉ bị loại bỏ, và không làm bất cứ điều gì.

Xác nhận ActionDispatch hy vọng bạn sẽ khớp một đường dẫn đầy đủ + động từ đến bộ hoàn chỉnh bộ điều khiển, hành động, & thông số đường dẫn.Vì vậy, các rspec matcher là chỉ đi qua những hạn chế của phương pháp nó đại biểu.

Có vẻ như trình kết hợp route_to được tích hợp sẵn của rspec sẽ không thực hiện những gì bạn muốn. Vì vậy, đề nghị tiếp theo sẽ là giả sử ActionDispatch sẽ làm những gì nó được cho là để làm, và thay vào đó chỉ cần viết thông số kỹ thuật cho lớp học của bạn ApiConstraints.

Để làm điều đó, trước tiên tôi khuyên bạn nên không bằng cách sử dụng mặc định spec_helper. Corey Haines có một ý kiến ​​hay về how to make a faster spec helper that doesn't spin up the whole rails app. Nó có thể không hoàn hảo cho trường hợp của bạn, nhưng tôi chỉ nghĩ rằng tôi sẽ chỉ ra nó vì bạn chỉ cần instantiating các đối tượng ruby ​​cơ bản ở đây và không thực sự cần bất kỳ ma thuật đường ray. Bạn cũng có thể thử yêu cầu các phụ thuộc ActionDispatch::Request & nếu bạn không muốn loại bỏ đối tượng yêu cầu như tôi làm ở đây.

Đó sẽ giống như

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 Tôi sẽ cho bạn tìm ra chính xác làm thế nào để thiết lập bối cảnh/viết những kỳ vọng cho các xét nghiệm khác của bạn.

+0

Có, điều này là chính xác. Lý tưởng nhất, tôi muốn ba thử nghiệm trong tệp api spec của tôi, một kiểm tra định dạng mặc định hoạt động, một để kiểm tra xem nó định tuyến đến một bộ điều khiển hợp lệ khi không có phiên bản nào được chỉ định và một để kiểm tra xem nó có định tuyến thích hợp hay không phiên bản IS được chỉ định. –

+1

Vâng, bằng cách sử dụng 'route_to' bạn cần cung cấp các kỳ vọng cụ thể hơn, như' mong đợi (: get => "/api/posts.json"').to route_to (: controller =>" api/v1/posts ",: action => "index",: format => "json") '. Thật không may là không có cách nào xung quanh điều đó với các đối sánh rspec-ray mặc định. – gregates

+0

Vấn đề với điều đó là mọi thông số kỹ thuật sẽ kiểm tra logic từ mọi thông số khác. Đó là cơ bản cán tất cả các thông số kỹ thuật vào một thử nghiệm, đó không phải là lý tưởng. –

Các vấn đề liên quan