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

我写了一个Rails 3.1引擎与命名空间岗位。 因此,我的控制器可以在app / controllers / posts /中find,我在app / models / posts中的模型等。我可以很好的testing模型。 一个模型的规格看起来像…

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

…一切正常。

但是,控制器的规格不起作用。 Rails引擎安装在/ posts,但控制器是Posts :: PostController。 因此,testing将查找控制器路由为posts / posts。

  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 

我已经尝试了各种各样的技巧在testing应用程序的路线文件…:命名空间等,无济于事。

我该如何做这项工作? 它似乎不会,因为引擎把控制器放在/职位,但名称空间将控制器放在/职位/职位为testing目的。

我假设你正在用虚拟rails应用testing你的引擎,就像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符号应该是你的引擎的名字,而不是它所在的path。

这是ActionDispatch::Routing::RouteSet::Generator#initialize因为get方法参数直接传递给ActionDispatch::Routing::RouteSet::Generator#initialize (定义在这里 ),这反过来使用@named_routeRack::Mount::RouteSet#generate获得正确的路由Rack::Mount::RouteSet#generate (看到这里和这里 )。

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

HTH

我通过覆盖所提供的getpostputdelete方法来解决这个问题,使得它总是将use_route作为parameter passing。

我以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 

使用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官方文档

基于这个答案,我select了以下解决scheme:

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

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

当您没有或无法使用isolate_namespace时,解决问题的isolate_namespace

 module Posts class Engine < Rails::Engine end end 

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

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

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

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

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

我遇到了类似的错误

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

事实certificate,我只是需要重新定义我的路线下划线的情况下,使RSpec可以匹配它。

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

我猜Rspec控制器testing使用反向查找基于下划线的情况下,而Rails将设置和解释的路线,如果你在camelcase或下划线的情况下定义它。