开发者

How do I write a Rails 3.1 engine controller test in rspec?

I have written a Rails 3.1 engine with the namespace Posts. Hence, my controllers are found in app/controllers/posts/, my models in app/models/posts, etc. I can test the models just fine. The spec for one model looks like...

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

... and everything works fi开发者_运维问答ne.

However, the specs for the controllers do not work. The Rails engine is mounted at /posts, yet the controller is Posts::PostController. Thus, the tests look for the controller route to be 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

which yields...

  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

I've tried all sorts of tricks in the test app's routes file... :namespace, etc, to no avail.

How do I make this work? It seems like it won't, since the engine puts the controller at /posts, yet the namespacing puts the controller at /posts/posts for the purpose of testing.


I'm assuming you're testing your engine with a dummy rails app, like the one that would be generated by enginex.

Your engine should be mounted in the dummy app:

In spec/dummy/config/routes.rb:

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

My second assumption is that your engine is isolated:

In lib/posts.rb:

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

I don't know if these two assumptions are really required, but that is how my own engine is structured.

The workaround is quite simple, instead of this

get :show, :id => 1

use this

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

The :posts symbol should be the name of your engine and NOT the path where it is mounted.

This works because the get method parameters are passed straight to ActionDispatch::Routing::RouteSet::Generator#initialize (defined here), which in turn uses @named_route to get the correct route from Rack::Mount::RouteSet#generate (see here and here).

Plunging into the rails internals is fun, but quite time consuming, I would not do this every day ;-) .

HTH


I worked around this issue by overriding the get, post, put, and delete methods that are provided, making it so they always pass use_route as a parameter.

I used Benoit's answer as a basis for this. Thanks buddy!

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


Use the rspec-rails routes directive:

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.


Based on this answer I chose the following solution:

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

The additional benefit is, that you don't need to have the before(:each) block in every controller-spec.


Solution for a problem when you don't have or cannot use isolate_namespace:

module Posts
  class Engine < Rails::Engine
  end
end

In controller specs, to fix routes:

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

Rails adds _engine to your app routes if you don't use isolate_namespace.


I'm developing a gem for my company that provides an API for the applications we're running. We're using Rails 3.0.9 still, with latest Rspec-Rails (2.10.1). I was having a similar issue where I had defined routes like so in my Rails engine gem.

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

I was getting an error like

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

It turns out I just needed to redefine my route in underscore case so that RSpec could match it.

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

I guess Rspec controller tests use a reverse lookup based on underscore case, whereas Rails will setup and interpret the route if you define it in camelcase or underscore case.


It was already mentioned about adding routes { MyEngine::Engine.routes }, although it's possible to specify this for all controller tests:

# spec/support/test_helpers/controller_routes.rb
module TestHelpers
  module ControllerRoutes
    extend ActiveSupport::Concern

    included do
      routes { MyEngine::Engine.routes }
    end

  end
end

and use in rails_helper.rb:

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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜