17

我正在开发一个主要用作 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

在每条路线中,我的约束是一个新的 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/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 规范”:

  • 它应该将未版本化的请求路由到最新版本
  • 如果没有指定,它应该默认为 JSON 格式
  • 它应该在请求时返回指定的 API 版本

有没有人有为这类路线编写规范的经验?我不想为 API 中的每个控制器添加规范,因为它们不负责此功能。

4

1 回答 1

5

Rspec 的route_to匹配器委托给ActionDispatch::Assertions::RoutingAssertions#assert_recognizes

的参数route_to作为expected_options散列传入(经过一些预处理,使其也能理解速记风格的参数,如items#index)。

您期望与匹配route_to器匹配的哈希(即{:get => "/api/posts", :format => "json"})实际上不是expect. 如果您查看源代码,您可以看到我们获得了匹配的路径 via

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

#first是一个确定的信号,表明我们期待一个只有一个键值对的散列。所以该:format => "json"组件实际上只是被丢弃了,并没有做任何事情。

断言希望您将ActionDispatch完整的路径 + 动词与一组完整的控制器、操作和路径参数相匹配。所以 rspec 匹配器只是传递了它委托给的方法的限制。

听起来 rspec 的内置route_to匹配器不会做你想做的事。所以下一个建议是假设ActionDispatch会做它应该做的事情,而不是为你的ApiConstraints班级编写规范。

为此,我首先建议不要使用默认的spec_helper. Corey Haines 有一个很好的要点,关于如何制作一个不会启动整个 rails 应用程序的更快的规范助手。它可能不适合您的情况,但我只是想指出它,因为您只是在这里实例化基本的 ruby​​ 对象并且并不真正需要任何 rails 魔法。ActionDispatch::Request如果您不想像我在这里做的那样存根请求对象,您也可以尝试要求& 依赖项。

那看起来像

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 我会让你弄清楚如何设置上下文/为你的其他测试编写期望。

于 2013-04-17T19:59:22.417 回答