Integration testing engines26 Apr 2012
I gave a lightning talk at Railsconf in response to a talk done by Andy Maleh about engines. His talk contained some content that I strongly disagreed with, and so I felt it necessary to set the record straight. You can see the slides for it here. I will have a more thorough dissection of the talk once the video is online.
To be perfectly honest, I am probably bitter because I was going to submit a talk about engines and then decided to submit another talk instead which ultimately didn't get accepted. Nevertheless...
The first thing that I can recall disagreeing with is Andy's suggestion to draw routes for the engine on the
Rails.application.routes object, by putting
this code inside the
config/routes.rb file of the engine:
Rails.application.routes.draw do resources :people end
The problem with this is that if the application's routes file has a catch-all route at the bottom, the route for the engine won't get matched because it is drawn after the application.
This leads to additional complexity as well, as by drawing these routes on the application like this, they would also correspond to a controller that's at the
top-level of the namespace, i.e.
PeopleController. This can lead to confusion as to where the controller is located. How can someone know if
PeopleController is inside the engine or is inside the application? Furthermore, the more engines that get added to this application, the more confusing such a thing would get.
Spree::Core::Engine.routes.draw do resources :products end
And by using
isolate_namespace inside your engine, the routes will be a completely separate entity for each engine, rather than one big "ball o' mud". In
Spree this namespace isolation is to the
Spree module (i.e.
isolate_namespace Spree), so that the controllers will be
Spree::ProductsController and not
Spree::Core::ProductsController and the like.
By using a completely isolated namespace like this, your controller would end up being correctly namespaced and then there's no confusion as to which engine
the controller is located within. You can then mount your engine at whatever path you want inside your application by explicitly defining the mount point
config/application.rb like this:
mount Spree::Core::Engine, :at => "/"
By mounting it at root, it will seem as if the engine were a part of the application itself. If you wanted to change where the engine was mounted, it's a
simple matter of changing the
:at: option and you can carry on like normal.
There's a whole bunch of other stuff that
isolate_namespace does as well, like namespacing of models and tables. It's absolutely important that you namespace
your engine to avoid confusion.
Oh, and one more thing: by doing this namespacing, if you want to get to an engine route from inside the application, you'll need to call the engine's routing proxy method, like this:
If you attempted to do
products_path it would go to the application's
products_path, which may be undefined.
Similarly, to get to the application's routes from inside the engine, use
Switching back and forth
The second thing that I disagreed with during Andy's talk was that he said that when developing an engine you needed to constantly switch back and forth between the application and the engine. This is simply not true. If you are developing your engine correctly, the development of it can be done in complete isolation from a real application and instead depend on a dummy application. It should not be a requirement for you to have two separate projects coupled together like this so you can test one or the other.
How we do it in Forem is that we have a dummy application inside
spec/dummy which is generated by running
bundle exec rake forem:dummy_app. This
command creates a new dummy application with things like
User model already set up inside it. Inside Spree, there's a similar command called
bundle exec rake test_app which does basically the same thing.
With the dummy application inside the engine, the engine is mounted onto that application and the tests are then run against that application. No need to switch back and forth from application to engine.
If you have modifications for the engine inside your application, there's also a way to test that. With Spree, we provide a module called
Spree::Core::UrlHelpers that you can include into your
RSpec.configure block like this:
require 'spree/core/url_helpers' RSpec.configure do |c| c.include Spree::Core::UrlHelpers, :type => :integration end
Then in your integration tests it's simply a matter of referencing the Spree routes like this:
Now for when you want to test that a customization to a Spree controller is behaving as intended, we've got a module for that too. It's called
Spree::Core::TestingSupport::ControllerRequests and you can include it like this:
require 'spree/core/testing_support/controller_requests' RSpec.configure do |c| c.include Spree::Core::TestingSupport::ControllerRequests, :type => :controller end
Then you can write your controller specs like this:
describe Spree::ProductsController do it "can see all the products" do spree_get :index end end
This will then allow you to make requests to the Spree engine from inside your application's tests.
There's absolutely no reason to mount engine routes directly on
Rails.application.routes, nor is there a requirement to switch back and forth. Integration
testing an engine is extremely easy once you know how to do it.