Testing routes with Rails 3.1 engines

Either I’m doing something wrong, or there’s a bug with Rails 3.1 engine routes and route tests.

The problem is that tests like these don’t seem to work when dealing with an engine with the follow RSpec test (the engine name is “Magic” in this case):

describe Magic::PoniesController do
describe "routing" do
it "routes to #index" do
get("/magic/ponies").should route_to("magic/ponies#index")
end
end
end

Well, there are actually two problems, the second being that the path helpers aren’t available to the engine’s specs.

The routes are declared like this:

Magic::Engine.routes.draw do

Instead of like this:

Rails::Application.routes.draw do

And for some people, just changing that is enough—it imports those routes directly into the parent application root path. But I’m wanting an isolated engine that can be mounted in the parent application like this:

Rails.application.routes.draw do
mount Magic::Engine => "/rainbow"
end

Of course, there’s a fixed mount of “/magic” in the engine’s dummy application, so that we can test it effectively.

What’s the solution? Well, Jason Hamilton and I (but mostly him) have come up with a solution that basically just copies the engine routes into the application routes on an as-needed basis.

Here is Jason:

OK, so throw this in your engine lib and require it in the engine.rb:

module Magic
module Rails
module Engine
##
# Automatically append all of the current engine's routes to the main
# application's route set. This needs to be done for ALL functional tests that
# use engine routes, since the mounted routes don't work during tests.
#
# @param [Symbol] engine_symbol Optional; if provided, uses this symbol to
# locate the engine class by name, otherwise uses the module of the calling
# test case as the presumed name of the engine.
#
# @author Jason Hamilton (jhamilton@greatherorift.com)
# @author Matthew Ratzloff (matt@urbaninfluence.com)
def load_engine_routes(engine_symbol = nil)
if engine_symbol
engine_name = engine_symbol.to_s.camelize
else
# No engine provided, so presume the current engine is the one to load
engine_name = self.class.name.split("::").first.split("(").last
end
engine = ("#{engine_name}::Engine").constantize

# Append the routes for this module to the existing routes
::Rails.application.routes.disable_clear_and_finalize = true
::Rails.application.routes.clear!
::Rails.application.routes_reloader.paths.each { |path| load(path) }
::Rails.application.routes.draw do
resourced_routes = []

named_routes = engine.routes.named_routes.routes
unnamed_routes = engine.routes.routes - named_routes.values

engine.routes.routes.each do |route|
# Call the method by hand based on the symbol
path = "/#{engine_name.underscore}#{route.path}"
verb = route.verb.to_s.downcase.to_sym
requirements = route.requirements
if path_helper = named_routes.key(route)
requirements[:as] = path_helper
elsif route.requirements[:controller].present?
# Presume that all controllers referenced in routes should also be
# resources and append that routing on the end so that *_path helpers
# will still work
resourced_routes << route.requirements[:controller].gsub("#{engine_name.downcase}/", "").to_sym
end
send(verb, path, requirements) if respond_to?(verb)
end

# Add each route, once, to the end under a scope to trick path helpers.
# This will probably break as soon as there is route name overlap, but
# we'll cross that bridge when we get to it.
resourced_routes.uniq!
scope engine_name.downcase do
resourced_routes.each do |resource|
resources resource
end
end
end

# Finalize the routes
::Rails.application.routes.finalize!
::Rails.application.routes.disable_clear_and_finalize = false
end
end
end
end
end

Rails::Engine.send(:include, Magic::Rails::Engine)

Call it whatever you like. I should probably create a gem for it, but ehhh… I think this will get fixed soon enough.

Anyway, in your spec_helper.rb (or test_helper.rb) call it like this:

Magic::Engine.load_engine_routes

Ta-da! Tests are passing, children are singing, and all is right with the world.

This post has been updated to fix named route handling.

Like this post? You might also like Coalmine, my centralized error tracking service for your apps. Coalmine captures errors and all kinds of helpful debugging information, notifies you, and makes it all searchable. Check it out!

5 comments

  1. thank you! thank you!

  2. Thanks for your snippet, it helped me a lot but…

    your code isn’t working at all when having a namespace in routes.
    It try to execute “resources :’namespace/resource’” (admin/categories for instance).

    You will find a patch here:
    https://github.com/mulasse/formol/blob/master/lib/formol/rails/engine.rb

    Best regards

  3. Xavier Dutreilh

    With Rails 3.2.0, this code does not work any more :.
    After some hacking, I found what has changed in Rails and made a fix. Details can be found in this commit:
    https://github.com/asellus/persona/commit/e0a73cc61942345b24f6186b5928c922072a7a44

  4. Great work all! Now Xavier please send an updated link to your 3.2 updates.

  5. I think this a great solution but complicated… just need to add the next line

    config.include MyEngine::Engine.routes.url_helpers

    on the spec_helper.rb file.

    Cheers!!!

Leave a comment