How do I write a Rails 3.1 engine controller test in rspec?

Martin Streicher picture Martin Streicher · Mar 5, 2011 · Viewed 9.6k times · Source

I have written a Rails 3.1 engine with the namespace Posts. Hence, my controllers are found in app/controllers/posts/, my models in app/models/posts, etc. I can test the models just fine. The spec for one model looks like...

module Posts
  describe Post do
    describe 'Associations' do
      it ...
      end

... and everything works fine.

However, the specs for the controllers do not work. The Rails engine is mounted at /posts, yet the controller is Posts::PostController. Thus, the tests look for the controller route to be posts/posts.

  describe "GET index" do
    it "assigns all posts as @posts" do
      Posts::Post.stub(:all) { [mock_post] }
       get :index
       assigns(:posts).should eq([mock_post])
    end
  end

which yields...

  1) Posts::PostsController GET index assigns all posts as @posts
     Failure/Error: get :index
     ActionController::RoutingError:
     No route matches {:controller=>"posts/posts"}
     # ./spec/controllers/posts/posts_controller_spec.rb:16

I've tried all sorts of tricks in the test app's routes file... :namespace, etc, to no avail.

How do I make this work? It seems like it won't, since the engine puts the controller at /posts, yet the namespacing puts the controller at /posts/posts for the purpose of testing.

Answer

Benoit Garret picture Benoit Garret · Apr 29, 2011

I'm assuming you're testing your engine with a dummy rails app, like the one that would be generated by enginex.

Your engine should be mounted in the dummy app:

In spec/dummy/config/routes.rb:

Dummy::Application.routes.draw do
  mount Posts::Engine => '/posts-prefix'
end

My second assumption is that your engine is isolated:

In lib/posts.rb:

module Posts
  class Engine < Rails::Engine
    isolate_namespace Posts
  end
end

I don't know if these two assumptions are really required, but that is how my own engine is structured.

The workaround is quite simple, instead of this

get :show, :id => 1

use this

get :show, {:id => 1, :use_route => :posts}

The :posts symbol should be the name of your engine and NOT the path where it is mounted.

This works because the get method parameters are passed straight to ActionDispatch::Routing::RouteSet::Generator#initialize (defined here), which in turn uses @named_route to get the correct route from Rack::Mount::RouteSet#generate (see here and here).

Plunging into the rails internals is fun, but quite time consuming, I would not do this every day ;-) .

HTH