如何使用rspec测试路径约束

时间:2013-04-17 18:55:04

标签: ruby-on-rails ruby-on-rails-3 constraints rails-routing

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

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

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

1 个答案:

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