Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 457
Martin Streicher Avatar asked Mar 05 '11 00:03

Martin Streicher


People also ask

How do I test a rails controller?

The currently accepted way to test rails controllers is by sending http requests to your application and writing assertions about the response. Rails has ActionDispatch::IntegrationTest which provides integration tests for Minitest which is the Ruby standard library testing framework.

What is Rspec rails gem?

rspec-rails is a testing framework for Rails 3. x and 4.


3 Answers

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

like image 151
Benoit Garret Avatar answered Oct 12 '22 09:10

Benoit Garret


I worked around this issue by overriding the get, post, put, and delete methods that are provided, making it so they always pass use_route as a parameter.

I used Benoit's answer as a basis for this. Thanks buddy!

module ControllerHacks
  def get(action, parameters = nil, session = nil, flash = nil)
    process_action(action, parameters, session, flash, "GET")
  end

  # Executes a request simulating POST HTTP method and set/volley the response
  def post(action, parameters = nil, session = nil, flash = nil)
    process_action(action, parameters, session, flash, "POST")
  end

  # Executes a request simulating PUT HTTP method and set/volley the response
  def put(action, parameters = nil, session = nil, flash = nil)
    process_action(action, parameters, session, flash, "PUT")
  end

  # Executes a request simulating DELETE HTTP method and set/volley the response
  def delete(action, parameters = nil, session = nil, flash = nil)
    process_action(action, parameters, session, flash, "DELETE")
  end

  private

  def process_action(action, parameters = nil, session = nil, flash = nil, method = "GET")
    parameters ||= {}
    process(action, parameters.merge!(:use_route => :my_engine), session, flash, method)
  end
end

RSpec.configure do |c|
  c.include ControllerHacks, :type => :controller
end
like image 36
Ryan Bigg Avatar answered Oct 12 '22 09:10

Ryan Bigg


Use the rspec-rails routes directive:

describe MyEngine::WidgetsController do
  routes { MyEngine::Engine.routes }

  # Specs can use the engine's routes & named URL helpers
  # without any other special code.
end

– RSpec Rails 2.14 official docs.

like image 24
Mars Avatar answered Oct 12 '22 10:10

Mars