如何在rspec中编写Rails 3.1引擎控制器测试?

时间:2011-03-05 00:12:07

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

我编写了一个带有命名空间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

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

如何使这项工作?似乎它不会,因为引擎将控制器置于/ post,但命名空间将控制器置于/ posts / posts以进行测试。

7 个答案:

答案 0 :(得分:42)

我假设您正在使用虚拟导轨应用测试您的引擎,就像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

答案 1 :(得分:22)

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

我用Benoit的答案作为基础。谢谢伙计!

use_route

答案 2 :(得分:18)

使用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

答案 3 :(得分:5)

基于this回答,我选择了以下解决方案:

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

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

答案 4 :(得分:2)

当您没有或不能使用isolate_namespace时解决问题:

module Posts
  class Engine < Rails::Engine
  end
end

在控制器规格中,修复路线:

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

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

答案 5 :(得分:0)

我正在为我的公司开发一个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控制器测试使用基于下划线情况的反向查找,而如果您在camelcase或下划线情况下定义它,Rails将设置和解释该路由。

答案 6 :(得分:0)

已经提到过要添加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