Rspec testing conditional routing constraints - ruby-on-rails

I'm trying to write some rspec integration tests to test that my conditional routes are routing correctly, but I'm getting a bunch of problems.
In routes.rb:
root :to => "error#ie6", :constraints => {:user_agent => /MSIE 6/}
root :to => "protocol_sets#index", :constraints => UserRoleConstraint.new(/doctor/i)
root :to => "refill_requests#create", :constraints => UserRoleConstraint.new(/member/i)
root :to => "refill_requests#create", :constraints => {:subdomain => "demo"}
root :to => "site#index"
In spec/requests/homepage_routing_spec.rb
require 'spec_helper'
describe "User Visits Homepage" do
describe "Routings to homepage" do
it "routes / to site#index when no session information exists" do
visit root_path
end
end
end
I get the following error when I try to run the test.
Failures:
1) User Visits Homepage Routings to homepage routes / to site#index when no session information exists
Failure/Error: visit root_path
NoMethodError:
undefined method `match' for nil:NilClass
# :10:in `synchronize'
# ./spec/requests/homepage_routings_spec.rb:6:in `block (3 levels) in '
Finished in 0.08088 seconds
1 example, 1 failure
Failed examples:
rspec ./spec/requests/homepage_routings_spec.rb:5 # User Visits Homepage Routings to homepage routes / to site#index when no session information exists
From Googling around I'm guessing there may be a problem with how rspec/capybara handle conditional routes.
Is there anyway to test constraints on routes with rspec and capybara?

As this drove me nuts over the last days, I found the solution with the help of a colleague.
When using named route constraints, like UserRoleConstraint in the example, I resorted to actually stubbing the matches? method of the constraint int he specs that needed it, i.e.:
describe 'routing' do
context 'w/o route constraint' do
before do
allow(UserRoleConstraint).to receive(:matches?).and_return { false }
end
# tests without the route constraint
end
context 'w/ route constraint' do
before do
allow(UserRoleConstraint).to receive(:matches?).and_return { true }
end
end
# tests with the route constraint
end
Note that this requires you to have named constraints, which might not apply to your case.

For the protocol constraint you can just specify an entire url with dummy domain. See this answer.

Related

Rspec not finding routes that exists

I'm trying to write a controller test and Rspec isn't finding routes that I know exist and work fine on a development server.
In my routes I have a catch-all route that should redeirect to a generic controller if someone goes to a route that isn't predefined.
routes.rb
namespace :tools do
match '*unmatchedpath' => "generic#show", :via => :get
end
generic_controller.rb
def show
# do stuff
end
generic_controller_spec.rb
require 'spec_helper'
describe Tools::GenericController do
describe 'GET show' do
it 'does stuff' do
get :show
end
end
Here is the error I get from rspec when I run the test above
1) Tools::GenericController GET show does stuff
Failure/Error: get :show
ActionController::RoutingError:
No route matches {:controller=>"tools/generic", :action=>"show"}
All routes work as expected on my development server so I'm not sure why Rspec isn't finding the route.
Try:
get '*unmatchedpath' => 'tools/generic#show'

Route works in browser, fails in Rspec

This question has probably been asked a dozen times on Stack Overflow (e.g. (1), (2), (3), (4), (5)) but every time the answer seems to be different and none of them have helped me. I'm working on a Rails Engine and I'm finding that Rspec2 gets route errors, but I can reach the routes in the browser. Here's the situation:
In the engine's routes.rb:
resources :mw_interactives, :controller => 'mw_interactives', :constraints => { :id => /\d+/ }, :except => :show
# This is so we can build the InteractiveItem at the same time as the Interactive
resources :pages, :controller => 'interactive_pages', :constraints => { :id => /\d+/ }, :only => [:show] do
resources :mw_interactives, :controller => 'mw_interactives', :constraints => { :id => /\d+/ }, :except => :show
end
Excerpted output of rake routes:
new_mw_interactive GET /mw_interactives/new(.:format) lightweight/mw_interactives#new {:id=>/\d+/}
...
new_page_mw_interactive GET /pages/:page_id/mw_interactives/new(.:format) lightweight/mw_interactives#new {:id=>/\d+/, :page_id=>/\d+/}
And my test, from one of the controller specs (describe Lightweight::MwInteractivesController do):
it 'shows a form for a new interactive' do
get :new
end
...which gets this result:
Failure/Error: get :new
ActionController::RoutingError:
No route matches {:controller=>"lightweight/mw_interactives", :action=>"new"}
...and yet when I go to that route in the browser, it works exactly as intended.
What am I missing here?
ETA: To clarify a point Andreas raises: this is a Rails Engine, so rspec runs in a dummy application which includes the engine's routes in a namespace:
mount Lightweight::Engine => "/lightweight"
...so the routes shown in rake routes are prefaced with /lightweight/. That's why the route shown in the Rspec error doesn't seem to match what's in rake routes. But it does make the debugging an extra step wonkier.
ETA2: Answering Ryan Clark's comment, this is the action I'm testing:
module Lightweight
class MwInteractivesController < ApplicationController
def new
create
end
...and that's it.
I found a workaround for this. Right at the top of the spec, I added this code:
render_views
before do
# work around bug in routing testing
#routes = Lightweight::Engine.routes
end
...and now the spec runs without the routing error. But I don't know why this works, so if someone can post an answer which explains it, I'll accept that.
I think the might be something wrong higher up in you specs
how did the "lightweight" get into this line :controller=>"lightweight/mw_interactives"
the route says
new_mw_interactive GET /mw_interactives/new(.:format)
not
new_mw_interactive GET /lightweight/mw_interactives/new(.:format)
add a file spec/routing/root_routing_spec.rb
require "spec_helper"
describe "routes for Widgets" do
it "routes /widgets to the widgets controller" do
{ :get => "/" }.should route_to(:controller => "home", :action => "index")
end
end
then add a file spec/controllers/home_controller_spec.rb
require 'spec_helper'
describe HomeController do
context "GET index" do
before(:each) do
get :index
end
it {should respond_with :success }
it {should render_template(:index) }
end
end

Controller spec failing to find routes defined by my Rails engine

I'm building a Rails engine under Rails 3.0.12, but I'm having issues with routes when trying to write specs for my engine's controller.
The context
I've been following an Enginex layout. The engine is named Featuring and is not isolated. It does not declare routes by itself: there is no featuring/config/routes.rb file. Instead, a routes_for_feature method is provided for the main application to define engine-specific routes.
##
# featuring/lib/featuring/rails.rb
#
require 'featuring/rails/routing'
module Featuring
class Engine < ::Rails::Engine
end
end
##
# featuring/lib/featuring/rails/routing.rb
#
module ActionDispatch::Routing
class Mapper
def routes_for_feature(feature_name)
resource_name = feature_name.to_s.pluralize.to_sym
resources resource_name, :controller => "featuring/features", :only => [:index, :show], :feature => feature_name.to_s
end
end
end
Following the Enginex template, I have a Dummy app which define the routes as so:
# featuring/spec/dummy/config/routes.rb
Dummy::Application.routes.draw do
routes_for_feature :feature_model
end
The issue
Everything is working fine when I run the rails server for the Dummy app. I can browse to http://localhost:3000/feature_models and the request is successful.
I would like to spec my Featuring::FeaturesController, but I can't get it to find the routes.
Here is the spec:
# featuring/spec/controllers/features_controller_spec.rb
require 'spec_helper'
describe Featuring::FeaturesController do
context "feature_models" do
it "GET index should be successful" do
puts Rails.application.routes.routes
get :index, { :use_route => "featuring", :feature => "feature_models" }
response.should be_success
end
end
end
And here is the result of running this spec:
rspec spec/controllers/features_controller_spec.rb:7
Featuring::FeaturesController
feature_models
GET /feature_models(.:format) {:action=>"index", :controller=>"featuring/features"}
GET /feature_models/:id(.:format) {:action=>"show", :controller=>"featuring/features"}
GET index should be successful (FAILED - 1)
Failures:
1) Featuring::FeaturesController feature_models GET index should be successful
Failure/Error: get :index, { :use_route => "featuring", :feature => "feature_models" }
ActionController::RoutingError:
No route matches {:feature=>"feature_models", :controller=>"featuring/features"}
# ./spec/controllers/features_controller_spec.rb:8:in `block (3 levels) in <top (required)>'
As you can see, even if the routes are correctly defined, the spec'ed controller seems not to find them.
Something surprises me in the RoutingError: No route matches {:feature=>"feature_models", :controller=>"featuring/features"}. The action => "index" is not displayed.
I had a similar error, and I was also confused by the lack of {:action => "index"} in the route options. However, this turned out not to be the problem. ActionDispatch treats the lack of an :action as equivalent to {:action => "index"}. See:
https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/routing/route_set.rb#L545
You may be missing a request parameter in your spec, as was the case with me. Check the Parameters line in your server log when you load the page in the browser.
The trick is to add routes { } to your spec, like so:
describe Featuring::FeaturesController do
routes { Featuring::Engine.routes }
# ...
end
See also No Route Matches ... Rails Engine

How to test routes in a Rails 3.1 mountable engine

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.
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
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.
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
This worked for me:
# spec_helper.rb
RSpec.configure do |config|
config.include MyEngine::Engine.routes.url_helpers
end
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.
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.

Rails RSpec Routing: Testing actions in :except do NOT route

Fairly simple problem (I'd have thought), but I'm having some issues:
In Rails 3.1.0.rc6/RSpec 2.6.0, I'm trying to test the routing of my 'products' resource, routed like this:
resources :products, :except => [:edit, :update]
The routing for the valid actions works, but I want to ensure that the edit and update routes are not callable. Here's what I'm trying:
it "does not route to #edit" do
lambda { get("/products/1/edit") }.should raise_error
end
Failure/Error: lambda { get("/products/1/edit") }.should raise_error
expected Exception but nothing was raised
# ./spec/routing/products_routing_spec.rb:11:in `block (3 levels)
in '
...And yet, when I run
it "does not route to #edit" do
get("/products/1/edit").should_not route_to("products#edit", :id => "1")
end
I get
Failure/Error: get("/products/1/edit").should_not
route_to("products#edit", :id => "1")
ActionController::RoutingError:
No route matches "/products/1/edit"
Any idea what's going on here? I'm guessing this should be pretty simple, but I can't seem to figure it out.
I don't know why the lambda would fail, but I don't think the rspec-rails dsl is intended to be used like that. Have you tried something like this?
{ :get => "/products/1/edit" }.should_not be_routable
http://relishapp.com/rspec/rspec-rails/docs/routing-specs/be-routable-matcher
So you can't specify what it doesn't route to, but you can specify that it doesn't get routed.
Do you have a fallback route? Because that would explain why no error is thrown, but indeed trying to evaluate route_to("products#edit", :id => 1) would raise, because the route does not exist.

Resources