我正在开发一个主要用作API的应用程序(除了一些次要视图,例如会话/注册,这将是“标准”)。我喜欢在Railscast #350: Versioning an API中最终确定的方法,因此遵循它。我的路线看起来像:
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
在每个路由中,我的Constraint是一个新的ApiConstraints对象,它位于我的./lib
文件夹中。该课程如下:
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
现在,手动测试时,一切都按预期工作。在我的API中,每个版本可能有5到10个控制器,并且不希望测试API约束适用于每个单独的控制器,因为这没有意义。我正在寻找一个测试我的API约束的spec文件,但我不确定该规范的放置位置。
我已尝试添加spec/routing/api_spec.rb
文件来测试内容,但它无法正常工作,因为它抱怨某些内容未提供,如下所示:
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
即使控制器正确匹配,上述操作也会引发错误。它失败并出现以下错误:
The recognized options <{"format"=>"json", "action"=>"index", "controller"=>"api/v1/posts"}>
did not match <{"controller"=>"api/v1/posts"}>,
difference: <{"format"=>"json", "action"=>"index"}>.
请注意,控制器已正确确定,但由于我不想测试此测试中的格式和操作,因此会出错。我希望有3个“API规范”:
有没有人有为这些路线编写规格的经验?我不想为API中的每个控制器添加规范,因为它们不对此功能负责。
答案 0 :(得分:5)
Rspec的route_to
匹配代表ActionDispatch::Assertions::RoutingAssertions#assert_recognizes
route_to
的参数作为expected_options
哈希传入(经过一些预处理,允许它也理解像items#index
这样的速记式参数。)
您期望与route_to
匹配器匹配的哈希值(即{:get => "/api/posts", :format => "json"}
)实际上并不是expect
的格式良好的参数。如果您查看the source,就可以看到我们通过
path, query = *verb_to_path_map.values.first.split('?')
#first
是一个明确的信号,我们期待只有一个键值对的哈希值。所以:format => "json"
组件实际上只是被丢弃了,并没有做任何事情。
ActionDispatch
断言期望您将完整路径+动词与一整套控制器,动作和&amp;路径参数。所以rspec匹配器只是传递了它委托给它的方法的局限性。
听起来像rspec的内置route_to
匹配器不会做你想要的。因此,下一个建议是假设ActionDispatch
将执行它应该执行的操作,而只是为您的ApiConstraints
类编写规范。
为此,我首先建议不使用默认的spec_helper
。 Corey Haines对how to make a faster spec helper that doesn't spin up the whole rails app有一个很好的要点。它可能不适合你的情况,但我只是想我会指出它,因为你只是在这里实例化基本的ruby对象并且不需要任何轨道魔法。你也可以尝试ActionDispatch::Request
&amp;依赖关系,如果您不想像我在这里那样存根请求对象。
这看起来像
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我会让你弄清楚如何设置上下文/写下你对其他测试的期望。