没有路由匹配rspec的匿名控制器。

30

根据我对rspec规范的理解,我希望下面的示例能够通过测试。

describe ApplicationController do

  controller do
    def test
    end
  end

  it "calls actions" do
    get :test
  end

end

相反,它会失败并显示以下错误:

No route matches {:controller=>"anonymous", :action=>"test"}

我甚至尝试在路由文件中定义“匿名”控制器的路由,但都没有用。这里是否有什么我忽略的东西?这应该可以工作,不是吗?

6个回答

25
为了在匿名控制器规范中使用自定义路由,您需要在before块中修改路由集。RSpec已经在before块中使用resources :anonymous设置了RESTful路由,并在after块中恢复了原始路由。所以要获取您自己的路由,只需在@routes上调用draw并添加所需内容即可。
这是一个来自ApplicationController规范的示例,它测试rescue_from CanCan::AccessDenied
require 'spec_helper'

describe ApplicationController
  controller do
    def access_denied
      raise CanCan::AccessDenied
    end
  end

  before do
    @routes.draw do
      get '/anonymous/access_denied'
    end
  end

  it 'redirects to the root when access is denied' do
    get :access_denied
    response.should redirect_to root_path
  end

  it 'sets a flash alert when access is denied' do
    get :access_denied
    flash.alert.should =~ /not authorized/i
  end
end

更新

这方面的处理在 RSpec 2.12 左右有所改进。如果您使用的是 > 2.12,则无需再钩入 @routes

为匿名控制器绘制自定义路由


哇,这绝对应该成为被接受的答案。谢谢你帮我澄清了事情。 - Janko

21

我遇到了类似的问题。在我的情况下,解决方案是在测试中的get请求中包含一个:id参数。

例如:

get :test, :id => 1

检查您的路由并查看是否缺少某个参数(可能是:id),然后将其添加到测试中。


2
啊,没错~这很有道理,因为我发现索引和创建操作似乎可以工作,而任何其他名称都会导致路由错误(这些操作不需要ID)。我会进一步检查它。 - Lachlan Cotter
7
经过进一步调查发现,这只是一个部分解决方案。包括:id参数可以让规范工作正常——但是只有在使用标准的RESTful操作名称并期望获得ID时才有效。如果您正在使用任意操作名称(例如'test'),则无论如何都会出现路由错误。看起来Rails和RSpec在这个领域都做出了太多假设。控制器测试应该测试控制器。我们有路由规范来测试路由。 - Lachlan Cotter
2
仍然适用于Rails 3.1.0和rspec 2.7.0。我同意你的评论 - 为什么它假定我的操作名称? - Rob
这不起作用,因为路由不存在。默认路由仅包括常规的RESTful路由。 - Cluster

5

看起来rspec为您提供了一组可用的RESTful路由。因此,如果您在匿名控制器中仅使用标准操作名称,则不会遇到此问题。迄今为止,我从未有过使用除“index”之外的任何其他操作名称的原因。

describe ApplicationController do
  describe 'respond_with_foo' do
    controller do
      def index
        respond_with_foo
      end
    end

    it 'should respond with foo' do
      get :index
      response.should be_foo
    end
  end
end

4

我遇到了同样的问题,我阅读了这个(在这里定义的)controller方法代码:

def controller(base_class = nil, &body)
  base_class ||= RSpec.configuration.infer_base_class_for_anonymous_controllers? ?
                   controller_class :
                   ApplicationController

  metadata[:example_group][:described_class] = Class.new(base_class) do
    def self.name; "AnonymousController"; end
  end
  metadata[:example_group][:described_class].class_eval(&body)

  # ADD ROUTES IN BEFORE BLOCK
  before do
    @orig_routes, @routes = @routes, ActionDispatch::Routing::RouteSet.new
    @routes.draw { resources :anonymous } # <==== HERE ARE THE ROUTES
  end

  after do
    @routes, @orig_routes = @orig_routes, nil
  end
end

代码中的大写注释是我的,基本上在使用此方法时只能使用标准RESTful路由。同时,在您的routes.rb文件中,您不能使用自定义路由,因为它们会被整个示例覆盖并在示例结束后恢复。


非常准确!它仅适用于标准的RESTful路由,无法本地自定义。 - brcebn

1

我曾经遇到同样的问题,并找到了一个对我很有效的解决方案。关键是要在测试规范中映射出这些路由:

require 'spec_helper'

describe ApplicationController do
  #Base class should be inferred
  controller do
    def not_found
      raise ActiveRecord::RecordNotFound
    end
  end

  def with_error_routing
    with_routing do |map|
      map.draw do
        get '/not_found' => "anonymous#not_found",     :as => :not_found
      end
      yield
    end
  end

  describe "handling ActiveRecord::RecordNotFound" do
    it "renders the 404 template" do
      with_error_routing do
        get :not_found
        response.should render_template 'error/404'
      end
    end
  end
end

0
我能够通过以下补丁解决这个问题:
module RSpec::Rails
  module ControllerExampleGroup
    module ClassMethods
      def controller(base_class = nil, &body)
        base_class ||= RSpec.configuration.infer_base_class_for_anonymous_controllers? ?
            controller_class :
            ApplicationController

        metadata[:example_group][:described_class] = Class.new(base_class) do
          def self.name; "AnonymousController"; end
        end
        metadata[:example_group][:described_class].class_eval(&body)

        ######## PATCH START ########
        custom_routes = @custom_routes # Copy over routes to local variable so it will be accessible
        before do
          @orig_routes, @routes = @routes, ActionDispatch::Routing::RouteSet.new
          if custom_routes # if this was set then pass that to the draw function
            @routes.draw &custom_routes
          else
            @routes.draw { resources :anonymous } # else do what it used to do before
          end
          ######### PATCH END #########

          routes = @routes
          described_class.send(:define_method, :_routes) { routes }
        end

        after do
          @routes, @orig_routes = @orig_routes, nil
        end
      end

      ######## PATCH START ########
      def custom_routes &block
        @custom_routes = block
      end
      ######### PATCH END #########
  end
end

结束

在你的spec_helper中要求该文件,然后在你的测试中简单地执行:

    describe ApplicationController do
      describe 'respond_with_foo' do
        custom_routes do
          get :bar, controller: :anonymous, action: :bar
        end
        controller do
          def index
            respond_with_foo
          end
        end

        it 'should respond with foo' do
          get :index
          response.should be_foo
        end
      end
    end

如果有人问,我测试了这段代码,并将infer_base_class_for_anonymous_controllers设置为false。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接