【问题标题】:How do I write a Rails 3.1 engine controller test in rspec?如何在 rspec 中编写 Rails 3.1 引擎控制器测试?
【发布时间】:2011-03-05 00:12:07
【问题描述】:

我用命名空间 Posts 编写了一个 Rails 3.1 引擎。因此,我的控制器位于 app/controllers/posts/ 中,我的模型位于 app/models/posts 等。我可以很好地测试模型。一个模型的规格看起来像......

module Posts
  describe Post do
    describe 'Associations' do
      it ...
      end

...一切正常。

但是,控制器的规格不起作用。 Rails 引擎安装在 /posts,但控制器是 Posts::PostController。因此,测试寻找控制器路由为帖子/帖子。

  describe "GET index" do
    it "assigns all posts as @posts" do
      Posts::Post.stub(:all) { [mock_post] }
       get :index
       assigns(:posts).should eq([mock_post])
    end
  end

产生...

  1) Posts::PostsController GET index assigns all posts as @posts
     Failure/Error: get :index
     ActionController::RoutingError:
     No route matches {:controller=>"posts/posts"}
     # ./spec/controllers/posts/posts_controller_spec.rb:16

我在测试应用程序的路由文件中尝试了各种技巧...:命名空间等,但无济于事。

我该如何进行这项工作?似乎不会,因为引擎将控制器放在/posts,但命名空间将控制器放在/posts/posts 以进行测试。

【问题讨论】:

    标签: ruby-on-rails-3 rspec ruby-on-rails-3.1 routes


    【解决方案1】:

    我假设您正在使用一个虚拟 Rails 应用程序测试您的引擎,就像 enginex 生成的那个。

    您的引擎应该安装在虚拟应用程序中:

    spec/dummy/config/routes.rb:

    Dummy::Application.routes.draw do
      mount Posts::Engine => '/posts-prefix'
    end
    

    我的第二个假设是您的引擎是孤立的:

    lib/posts.rb:

    module Posts
      class Engine < Rails::Engine
        isolate_namespace Posts
      end
    end
    

    我不知道这两个假设是否真的需要,但我自己的引擎就是这样构造的。

    解决方法很简单,而不是这个

    get :show, :id => 1
    

    使用这个

    get :show, {:id => 1, :use_route => :posts}
    

    :posts 符号应该是引擎的名称,而不是它的安装路径。

    之所以可行,是因为 get 方法参数直接传递给 ActionDispatch::Routing::RouteSet::Generator#initialize(定义为 here),而后者又使用 @named_routeRack::Mount::RouteSet#generate 获取正确的路由(参见 herehere) .

    陷入铁路内部很有趣,但相当耗时,我不会每天都这样做;-)。

    HTH

    【讨论】:

    • 这对我帮助很大。谢谢!
    • 我想知道是否可以将其设置为某种全局的某处,因此不必将其传递给每个测试...
    • 我也想知道你是怎么知道的。
    • @RyanBigg 一旦我的 google-fu 碰壁了,旧的故障转移:阅读源代码并在 Rails 控制台中进行大量的探索。我花了整整一个小时,因为这是我第一次阅读路由代码。
    • @RyanBigg 您可以将其设置为前块,如下所示:before(:each) { @routes = Posts::Engine.routes }。在这里找到它:stackoverflow.com/a/8140626/299781
    【解决方案2】:

    我通过覆盖提供的getpostputdelete 方法解决了这个问题,使它们始终将use_route 作为参数传递。

    我以 Benoit 的回答为基础。谢谢朋友!

    module ControllerHacks
      def get(action, parameters = nil, session = nil, flash = nil)
        process_action(action, parameters, session, flash, "GET")
      end
    
      # Executes a request simulating POST HTTP method and set/volley the response
      def post(action, parameters = nil, session = nil, flash = nil)
        process_action(action, parameters, session, flash, "POST")
      end
    
      # Executes a request simulating PUT HTTP method and set/volley the response
      def put(action, parameters = nil, session = nil, flash = nil)
        process_action(action, parameters, session, flash, "PUT")
      end
    
      # Executes a request simulating DELETE HTTP method and set/volley the response
      def delete(action, parameters = nil, session = nil, flash = nil)
        process_action(action, parameters, session, flash, "DELETE")
      end
    
      private
    
      def process_action(action, parameters = nil, session = nil, flash = nil, method = "GET")
        parameters ||= {}
        process(action, parameters.merge!(:use_route => :my_engine), session, flash, method)
      end
    end
    
    RSpec.configure do |c|
      c.include ControllerHacks, :type => :controller
    end
    

    【讨论】:

    • 比每次都指定这个要好得多,加书签,谢谢!
    • 非常感谢瑞恩。奇迹般有效。在出现更简单的方法之前会一直使用它。
    • @Inc1982:不用担心。乐于助人!
    【解决方案3】:

    使用 rspec-rails routes 指令:

    describe MyEngine::WidgetsController do
      routes { MyEngine::Engine.routes }
    
      # Specs can use the engine's routes & named URL helpers
      # without any other special code.
    end
    

    ——RSpec Rails 2.14 official docs.

    【讨论】:

      【解决方案4】:

      根据this 的回答,我选择了以下解决方案:

      #spec/spec_helper.rb
      RSpec.configure do |config|
       # other code
       config.before(:each) { @routes = UserManager::Engine.routes }
      end
      

      额外的好处是,您不需要在每个控制器规范中都有 before(:each) 块。

      【讨论】:

      • 这似乎不再起作用了。我对解决方案有相同的想法,但似乎没有什么不同。
      • 即使它确实有效,您也会破坏应用程序其余部分的所有其他测试。
      【解决方案5】:

      没有或无法使用isolate_namespace时的问题解决方案:

      module Posts
        class Engine < Rails::Engine
        end
      end
      

      在控制器规范中,修复路由:

      get :show, {:id => 1, :use_route => :posts_engine}   
      

      如果您不使用 isolate_namespace,Rails 会将 _engine 添加到您的应用程序路由中。

      【讨论】:

        【解决方案6】:

        我正在为我的公司开发一个 gem,它为我们正在运行的应用程序提供 API。我们仍在使用 Rails 3.0.9,最新的 Rspec-Rails (2.10.1)。我遇到了类似的问题,我在 Rails 引擎 gem 中定义了类似的路由。

        match '/companyname/api_name' => 'CompanyName/ApiName/ControllerName#apimethod'
        

        我遇到了类似的错误

        ActionController::RoutingError:
         No route matches {:controller=>"company_name/api_name/controller_name", :action=>"apimethod"}
        

        事实证明,我只需要在下划线的情况下重新定义我的路线,以便 RSpec 可以匹配它。

        match '/companyname/api_name' => 'company_name/api_name/controller_name#apimethod'
        

        我猜 Rspec 控制器测试使用基于下划线大小写的反向查找,而 Rails 将设置和解释路由,如果你用驼峰或下划线大小写定义它。

        【讨论】:

          【解决方案7】:

          关于添加routes { MyEngine::Engine.routes } 已经提到过,尽管可以为所有控制器测试指定它:

          # spec/support/test_helpers/controller_routes.rb
          module TestHelpers
            module ControllerRoutes
              extend ActiveSupport::Concern
          
              included do
                routes { MyEngine::Engine.routes }
              end
          
            end
          end
          
          

          并在rails_helper.rb中使用:

          RSpec.configure do |config|
            config.include TestHelpers::ControllerRoutes, type: :controller
          end
          

          【讨论】:

            猜你喜欢
            • 2011-11-19
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-01-19
            • 1970-01-01
            • 1970-01-01
            • 2012-05-13
            • 1970-01-01
            相关资源
            最近更新 更多