Mount additional routes in Rails specs - ruby-on-rails

I want to test 3rd party sites embedding a javascript widget (provided by my Rails app) and submitting forms to an endpoint in my rails app.
To do this, I have created a dummy sinatra app that I test with Capybara:
# spec/system/widget_spec.rb
require 'sinatra/base'
class DummyApp < Sinatra::Base
get '/form' do
#widget_code = params[:widget_code]
erb :form
end
end
require 'rails_helper'
...
I'm defining this app before Rails is loaded in my spec, so that I can do the following in routes.rb:
mount DummyApp => '/dummy' if defined? DummyApp
This works if I run just this one spec, but if I run the whole suite, Rails is loaded before my DummyApp is defined.
Ideally I'd like to pull this line out of routes.rb entirely and allow my test to inject custom routes, so my routes file isn't cluttered with test-related routes.
1) Is there a way to tinker with rails routes within tests?
2) If not, what's the best way to go about testing this type of scenario?

There is a helper with_routing you could use (docs):
with_routing do |set|
set.draw do
mount DummyApp => '/dummy'
end
# ...
end

Related

access path helper for an engine when testing parent Rails app with RSpec

I have a Rails engine and a Rails 4 app that contains it. I want to write an RSpec test in the parent Rails app that verifies one of the Rails app's controllers redirects to a path inside the engine. When I run rake routes for my Rails app, I see the engine path I want, and it's named. I can refer to that path inside a view in my Rails app by saying my_engine.my_engine_path. However, my_engine doesn't seem to be a thing inside my Rails app's RSpec controller test. I don't want to do the following in my app's spec_helper.rb because that seems to clobber my app's routes with my engine's:
config.include Module.new {
def self.included base
base.routes { MyEngine::Engine.routes }
end
}, type: :controller
When I do the following, I get undefined local variable or method 'my_engine':
expect(response).to redirect_to(my_engine.my_engine_path(some_route_param))
My problem was in my routes. I was using a path parameter instead of id because the controller deals with files on the file system, not database records. I had resources :my_controller instead of resources :my_controller, param: :path, constraints: {path: /.*/}. So doing expect(response).to redirect_to(my_engine_path(some_route_param)) works fine in my app's RSpec controller test, no my_engine. prefix necessary.

Integration testing of spree (alongside refinery)

I have just started a new rails app. So far there are no controllers or models there is simply the two engines: refinery and spree working next to each other.
They are mounted as following:
Store::Application.routes.draw do
mount Spree::Core::Engine, :at => '/shop'
mount Refinery::Core::Engine, :at => '/'
end
Now i have set up a few pages using refinery so when I go to / then i see the refinery home page and can click the about us page etc. When i go to /shop then i see the spree section of the site which is also working well.
Now i would like to write a small test that the spree engine is correctly mounted at '/shop'. I tried with the following test:
require 'test_helper'
class SpreeEngineTest < ActionDispatch::IntegrationTest
test "has been correctly mounted" do
get "/shop"
assert_response :success
end
end
But it fails with the result:
Expected response to be a <:success>, but was <302>
I looked into the body of the request and it contains the following:
"<html><body>You are being redirected.</body></html>"
I am using the standard testunit package and rails 3.2
Thanks for your help!
Maybe you should use assert_redirected_to instead of assert_response.
http://api.rubyonrails.org/classes/ActionDispatch/Assertions/ResponseAssertions.html
Sorry for the late answer. The reason is that Refinery will redirect you to the create user dialog when it detects that no users with the correct roles exists in the database. So my guess is that you're running this test without any users registered in the test database, and that causes the redirect.
In your integration test, add the following setup method:
def setup
user = Refinery::User.new(:username => 'testuser', :email => 'test#example.com', :password => 'test', :password_confirmation => 'test')
assert user.create_first
end
This will ensure that a default user with the right roles exist and the rest of the test should be ok.

Named routes in mounted rails engine

I'm making a small rails engine which I mount like this:
mount BasicApp::Engine => "/app"
Using this answer I have verified that all the routes in the engine are as the should be:
However - when I (inside the engine) link to a named route (defined inside the engine) I get this error
undefined local variable or method `new_post_path' for #<#<Class:0x000000065e0c08>:0x000000065d71d0>
Running "rake route" clearly verifies that "new_post" should be a named path, so I have no idea why Rails (3.1.0) can't figure it out. Any help is welcome
my config/route.rb (for the engine) look like this
BasicApp::Engine.routes.draw do
resources :posts, :path => '' do
resources :post_comments
resources :post_images
end
end
I should add that it is and isolated engine. However paths like main_app.root_path works fine - while root_path does not
The right way
I believe the best solution is to call new_post_path on the Engine's routes proxy, which is available as a helper method. In your case, the helper method will default to basic_app_engine, so you can call basic_app_engine.new_post_path in your views or helpers.
If you want, you can set the name in one of two ways.
# in engine/lib/basic_app/engine.rb:
module BasicApp
class Engine < ::Rails::Engine
engine_name 'basic'
end
end
or
# in app/config/routes.rb:
mount BasicApp::Engine => '/app', :as => 'basic'
In either case, you could then call basic.new_posts_path in your views or helpers.
Another way
Another option is to not use a mounted engine and instead have the engine add the routes directly to the app. Thoughtbot's HighVoltage does this. I don't love this solution because it is likely to cause namespace conflicts when you add many engines, but it does work.
# in engine/config/routes.rb
Rails.application.routes.draw do
resources :posts, :path => '' do
resources :post_comments
resources :post_images
end
end
# in app/config/routes.rb:
# (no mention of the engine)
On Rails 4 the engine_name directive did not work for me.
To access a named route defined in engine's routes from engine's own view or controller, I ended up using the verbose
BasicApp::Engine.routes.url_helpers.new_post_path
I recommend defining a simple helper method to make this more usable
# in /helpers/basic_app/application_helper.rb
module BasicApp::ApplicationHelper
def basic_app_engine
##basic_app_engine_url_helpers ||= BasicApp::Engine.routes.url_helpers
end
end
With this in place you can now use
basic_app_engine.new_post_path
In case you need to access your main application helper from the engine you can just use main_app:
main_app.root_path
use the below in you app to access the engine routes
MyApp::Engine.routes.url_helpers.new_post_path

Remove Routes Specified in a Gem?

Is there a way to remove routes specified in a gem in Rails 3? The exception logger gem specifies routes which I don't want. I need to specify constraints on the routes like so:
scope :constraints => {:subdomain => 'secure', :protocol => 'https'} do
collection do
post :query
post :destroy_all
get :feed
end
end
Based on the Rails Engine docs, I thought I could create a monkey patch and add a routes file with no routes specified to the paths["config/routes"].paths Array but the file doesn't get added to ExceptionLogger::Engine.paths["config/routes"].paths
File: config/initializers/exception_logger_hacks.rb
ExceptionLogger::Engine.paths["config/routes"].paths.unshift(File.expand_path(File.join(File.dirname(__FILE__), "exception_logger_routes.rb")))
Am I way off base here? Maybe there is a better way of doing this?
It is possible to prevent Rails from loading the routes of a specific gem, this way none of the gem routes are added, so you will have to add the ones you want manually:
Add an initializer in application.rb like this:
class Application < Rails::Application
...
initializer "myinitializer", :after => "add_routing_paths" do |app|
app.routes_reloader.paths.delete_if{ |path| path.include?("NAME_OF_GEM_GOES_HERE") }
end
Here's one way that's worked for me.
It doesn't "remove" routes but lets you take control of where they match. You probably want every route requested to match something, even if it is a catch all 404 at the bottom.
Your application routes (MyApp/config/routes.rb) will be loaded first (unless you've modified the default load process). And routes matched first will take precedence.
So you could redefine the routes you want to block explicitely, or block them with a catch all route at the bottom of YourApp/config/routes.rb file.
Named routes, unfortunately, seem to follow ruby's "last definition wins" rule. So if the routes are named and your app or the engine uses those names, you need to define the routes both first (so yours match first), and last (so named routes point as you intended, not as the engine defines.)
To redefine the engine's routes after the engine adds them, create a file called something like
# config/named_routes_overrides.rb
Rails.application.routes.draw do
# put your named routes here, which you also included in config/routes.rb
end
# config/application.rb
class Application < Rails::Application
# ...
initializer 'add named route overrides' do |app|
app.routes_reloader.paths << File.expand_path('../named_routes_overrides.rb',__FILE__)
# this seems to cause these extra routes to be loaded last, so they will define named routes last.
end
end
You can test this routing sandwich in the console:
> Rails.application.routes.url_helpers.my_named_route_path
=> # before your fix, this will be the engine's named route, since it was defined last.
> Rails.application.routes.recognize_path("/route/you/want/to/stop/gem/from/controlling")
=> # before your fix, this will route to the controller and method you defined, rather than what the engine defined, because your route comes first.
After your fix, these calls should match each other.
(I posted this originally on the refinery gem google group here: https://groups.google.com/forum/?fromgroups#!topic/refinery-cms/N5F-Insm9co)

Where to test routes in ruby on rails

Where to test routes in ruby on rails?
unit tests?
functional tests?
integration tests?
Addition:
To be exact, where to apply assertions described on guides and on api?
Routes should be done as part of integration tests. Integration tests are where you test the important work flows of your application - more specifically whether a URL is defined or not seems to be an important workflow.
Your integration test would look like any normal integration test:
# /tests/integration/routes_test.rb
require 'test_helper'
class RoutesTest < ActionController::IntegrationTest
test "route test" do
assert_generates "/photos/1", { :controller => "photos", :action => "show", :id => "1" }
assert_generates "/about", :controller => "pages", :action => "about"
end
end
As to #jemminger's response of not testing routes - While it is Rail's tests that verify that routes.rb works, it's not Rail's responsibility to test whether http://yoursite.com/users is defined in your routes. The caveat is that most route testing could be done in existing integration tests, so specific tests for routes could be redundant.
The specific use case I can think of are all the people that have already, or are going to upgrade from Rails 2 to Rails 3. The code to define routes has changed significantly, and it's better to find out from tests that the routes were upgraded correctly, than from users when they report 404 errors.
Why do you feel the need to test the routes? Purely to make sure that the routes defined in your routes.rb actually work? If so, then don't. That's not the job of your application's tests to make sure that the framework's internals operate properly - that's the job of the Rails framework's own tests.
If perhaps you have some sort of dynamic/user definable route that you want to test, I'd probably go with integration.
I suggest you create a test file for the routes in your test/controllers folder.
class HomeRoutesTest < ActionController::TestCase
test "must route to home index" do
assert_routing '/', controller: "home", action: "index"
end
end
It was mentioned that they belong to integration test. I disagree. You merely test routes, that's it. So it would rather fall into functional or even unit testing instead of integration testing.
You can find a reference in the Rails RailsGuide Testing, section 9.
Testing Routes
Integration tests are for the flow, where you test the interaction of different controller action, e.g. executing a business process like user logs in, browses the site and puts an item into the basket.
That said your integration tests won't work if your routes don't work. Thus many say that integration tests is the place where you test routes.
However, considering the development cycle, you would create unit tests, controller tests, route tests, etc. first before doing the integration tests.
And on another note: assert_routing does both tests: assert_generates and assert_recognizes.
According to a comment in this rails bug, the proper place is in a functional test. If you try to test them in an integration test, the routes you established in routes.rb will not be available.
require 'test_helper'
class FooControllerTest < ActionController::TestCase
test "foo routes" do
assert_recognizes({:controller => 'foo_controller', :action => 'list'}, '/foos'
end
end
Route tests are good places to list links that exist in the wild, and avoid inadvertently breaking them with a code change. It's a little strange that the tests are scoped to a controller, since it's the route set as a whole you're actually testing, but I haven't heard of a 'route set test'.
If you are using rspec, then the most natural place for routing tests is in the directory spec/routing. From The Rspec documentation:
Routing specs are marked by :type => :routing or if you have set
config.infer_spec_type_from_file_location! by placing them in spec/routing.

Resources