20

I am trying to write some routing specs for a mountable rails 3.1 engine. I have working model and controller specs, but I cannot figure out how to specify routes.

For a sample engine, 'testy', every approach I try ends with the same error:

 ActionController::RoutingError:
   No route matches "/testy"

I've tried both Rspec and Test::Unit syntax (spec/routing/index_routing_spec.rb):

describe "test controller routing" do
  it "Routs the root to the test controller's index action" do
    { :get => '/testy/' }.should route_to(:controller => 'test', :action => 'index')
  end

  it "tries the same thing using Test::Unit syntax" do
    assert_routing({:method => :get, :path => '/testy/', :use_route => :testy}, {:controller => 'test', :action => 'index'})
  end
end

I've laid out the routes correctly (config/routes.rb):

Testy::Engine.routes.draw do
  root :to => 'test#index'
end

And mounted them in the dummy app (spec/dummy/config/routes.rb):

Rails.application.routes.draw do
  mount Testy::Engine => "/testy"
end

And running rails server and requesting http://localhost:3000/testy/ works just fine.

Am I missing anything obvious, or is this just not properly baked into the framework yet?

Update: As @andrerobot points out, the rspec folks have fixed this issue in version 2.14, so I've changed my accepted answer accordingly.

Cameron Pope
  • 7,565
  • 2
  • 26
  • 24

6 Answers6

12

Since RSpec 2.14 you can use the following:

describe "test controller routing" do
  routes { Testy::Engine.routes }
  # ...
end

Source: https://github.com/rspec/rspec-rails/pull/668

andrerobot
  • 447
  • 4
  • 12
  • Thank you for the update! I created a mountable engine using rspec 2.14 and rails 4.0, and that worked like a charm. I've changed my accepted answer. – Cameron Pope Jul 11 '13 at 22:05
  • 2
    What if I need both the routes from the engine and the routes from the main_app? – Jwan622 Sep 08 '16 at 18:18
  • The problem is that the specific route is in the dummy app's routes. This solution does not include the dummy app's routes but instead include only the engine's routes. – konyak Dec 05 '19 at 22:03
11

Try adding a before block with the following:

before(:each) { @routes = Testy::Engine.routes }

That worked for me, as the routing specs use that top level instance variable to test their routes.

  • Thanks - that was very close, but replacing the 'default' routes with the engine's routes also required me to change my tests, so I upvoted your answer since it was the final clue that I needed. – Cameron Pope Nov 15 '11 at 17:09
11

The answer from Steven Anderson got me most of the way there, but the requests need to be made relative to the engine, rather than the app - probably because this technique replaces the app's routes with the engine's routes, so everything is now relative to the engine. It seems a little fragile to me, but I haven't seen another way that works. If someone posts a cleaner way of doing this, I'll be happy to accept that answer instead.

In the 'dummy' app, if the engine is mounted as follows (spec/dummy/config/routes.rb):

Rails.application.routes.draw do
  mount Testy::Engine => "/testy"
end

The following spec will correctly test the root route of the engine using both rspec and test::unit syntax (spec/routing/index_route_spec.rb):

require 'spec_helper'

describe "test controller routing" do
  before(:each) { @routes = Testy::Engine.routes }

  it "Routes the root to the test controller's index action" do
    { :get => '/' }.should route_to(:controller => 'testy/test', :action => 'index')
  end

  it "tries the same thing using Test::Unit syntax" do
    assert_routing({:method => :get, :path => '/'}, {:controller => 'testy/test', :action => 'index'})
  end
end
Cameron Pope
  • 7,565
  • 2
  • 26
  • 24
  • 1
    This. I'd like to be able to use something like `get: '/testy/'` in my specs to more closely match where I'm actually mounted the engine. But that doesn't seem possible and this answer seems to the current best way to get the specs passing. – Doug Johnston Feb 20 '15 at 22:15
  • This worked for me but for test::unit I placed the @routes assignment in my setup function. – mkrinblk Jan 04 '17 at 20:16
  • @dojosto did you find a solution for the `get: '/testy/'` request? I know it's an old answer, but I stumbled upon the exact problem today – 23tux Oct 25 '19 at 06:43
4

This worked for me:

# spec_helper.rb
RSpec.configure do |config|
  config.include MyEngine::Engine.routes.url_helpers
end
Sam Soffes
  • 14,831
  • 9
  • 76
  • 80
  • Unfortunately including the url helpers didn't change the error I was seeing: ActionController::RoutingError: No route matches "/testy" – Cameron Pope Nov 15 '11 at 17:08
1

For me, it was a combination of comments by pretty much everybody involved so far.

First, I started with this simple test:

  it "routes / to the widgets controller" do
    get('/').should route_to("mozoo/widget#index")
  end

This resulted in:

Failures:
  1) Mozoo::WidgetController GET widget index routes / to the widgets controller
     Failure/Error: get('/').should route_to("mozoo/widget#index")
     ActionController::RoutingError:
       No route matches {:controller=>"mozoo/widget", :action=>"/"}
     # ./spec/controllers/mozoo/widget_controller_spec.rb:9:in `block (3 levels) in <module:Mozoo>'

So I switched from get('/') to { :get => '/' } and things started working great. Not sure why. According to lib/rspec/rails/matchers/routing_matchers.rb L102-105, there is no difference, but it makes a difference to me. Regardless, thanks @cameron-pope.

Next, I added another pretty simple and very similar test as that above:

it "routes root_path to the widgets controller" do
  { :get => root_path }.should route_to("mozoo/widget#index")
end

And was getting this error:

Failures:
  1) Mozoo::WidgetController GET widget index routes root_path to the widgets controller
     Failure/Error: { :get => '/mozoo' }.should route_to("mozoo/widget#index")
       No route matches "/mozoo"
     # ./spec/controllers/mozoo/widget_controller_spec.rb:14:in `block (3 levels) in <module:Mozoo>'

So I added this:

before(:each) { @routes = Mozoo::Engine.routes }

And got a better/different error:

Failures:
  1) Mozoo::WidgetController GET widget index routes root_path to the widgets controller
     Failure/Error: { :get => root_path }.should route_to("mozoo/widget#index")
       The recognized options <{"controller"=>"mozoo/widget", "action"=>"index", "section"=>"mozoo"}> did not match <{"controller"=>"mozoo/widget", "action"=>"index"}>, difference: <{"section"=>"mozoo"}>.
       <{"controller"=>"mozoo/widget", "action"=>"index"}> expected but was
       <{"controller"=>"mozoo/widget", "action"=>"index", "section"=>"mozoo"}>.
     # ./spec/controllers/mozoo/widget_controller_spec.rb:14:in `block (3 levels) in <module:Mozoo>'

From there, I changed my test to include the section (the namespace my engine is under):

{ :get => root_path }.should route_to(:controller => "mozoo/widget", :action => "index", :section => "mozoo")

And viola, it passed. Thanks @steven-anderson.

This next part is odd. After adding another test for a specific widget which used the widget_path url helper for a named route:

  it "will successfully serve the widget show page" do
    visit widget_path(:foobar)
    response.should be_success
  end

The test promptly blowd up on me with:

Failures:
  1) GET bubble_summary_row widget will have the content section properly scoped
     Failure/Error: visit widget_path(:bubble_summary_row)
     NoMethodError:
       undefined method `widget_path' for #<RSpec::Core::ExampleGroup::Nested_3:0x0000010748f618>
     # ./spec/views/mozoo/widgets/show.html.haml_spec.rb:7:in `block (2 levels) in <module:Mozoo>'

So I added the following spec_helper config entry:

RSpec.configure do |config|
  config.include Testy::Engine.routes.url_helpers
end

And BAM! It passed. Thanks @sam-soffes. What makes this odd is that later on when creating this comment, I removed that config entry to try and get the error back and I was unable to reproduce the error simply by removing the config entry. Oh well, I'm moving on. Hopefully this long-winded account helps somebody.

ynkr
  • 25,946
  • 4
  • 32
  • 30
1

Based on this answer I chose the following solution:

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

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

Community
  • 1
  • 1
wintersolutions
  • 5,173
  • 5
  • 30
  • 51
  • 1
    This didn't work for me. Any idea why? If I add that before line in the test itself, it seems to work. – Karan Aug 02 '13 at 22:15