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

后端 未结 7 2002
北荒
北荒 2020-12-02 12:37

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 mod

相关标签:
7条回答
  • 2020-12-02 12:48

    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

    0 讨论(0)
  • 2020-12-02 12:49

    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
    
    0 讨论(0)
  • 2020-12-02 12:56

    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.

    0 讨论(0)
  • 2020-12-02 13:03

    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.

    0 讨论(0)
  • 2020-12-02 13:04

    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.

    0 讨论(0)
  • 2020-12-02 13:06

    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.

    0 讨论(0)
提交回复
热议问题