【问题标题】:How to test route constraints with rspec如何使用 rspec 测试路由约束
【发布时间】:2013-04-17 18:55:04
【问题描述】:

我正在开发一个主要用作 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 中的每个控制器添加规范,因为它们不负责此功能。

【问题讨论】:

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


    【解决方案1】:

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

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

    您期望匹配route_to 匹配器的哈希(即{:get =&gt; "/api/posts", :format =&gt; "json"})实际上不是expect 的格式正确的参数。如果你查看the source,你可以看到我们得到了匹配的路径通过

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

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

    ActionDispatch 断言希望您将完整的路径 + 动词与一组完整的控制器、操作和路径参数匹配。所以 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​​ 对象,并不需要任何 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 我会让你弄清楚如何设置上下文/为你的其他测试编写期望。

    【讨论】:

    • 是的,这是正确的。理想情况下,我希望在我的 api 规范文件中进行三项测试,一项测试默认格式是否有效,一项测试在未指定版本时它是否路由到有效控制器,另一项测试它是否在未指定版本时路由到正确版本版本已指定。
    • 好吧,使用route_to,您需要提供更具体的期望,例如expect(:get =&gt; "/api/posts.json"').to route_to(:controller =&gt; "api/v1/posts", :action =&gt; "index", :format =&gt; "json")。不幸的是,使用默认的 rspec-rails 匹配器无法解决这个问题。
    • 这样的问题是每个规范都将测试每个其他规范的逻辑。它本质上是将所有规格整合到一个测试中,这并不理想。
    • 是的,我感觉到你,只是说rspec的路由规范dsl非常有限。感觉用的不多。可能是 rspec-rails 补丁的一个不错的主题。
    • 另外,我怀疑有些人会建议您只在 ApiConstraint 类上测试 #matches? 方法,并相信 ActionDispatch 会做它的事情。
    猜你喜欢
    • 2011-04-20
    • 1970-01-01
    • 1970-01-01
    • 2010-12-18
    • 1970-01-01
    • 2011-08-21
    • 2011-08-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多