Test Ruby-on-Rails controller with RSpec and different route name - ruby-on-rails

I have a Rails model named Xpmodule with a corresponding controller XpmoduleController.
class XpmoduleController < ApplicationController
def index
#xpmodule = Xpmodule.find(params[:module_id])
end
def subscribe
flash[:notice] = "You are now subscribed to #{params[:subscription][:title]}"
redirect_to :action => :index
end
end
The original intent was to name the model Module which for obvious reasons doesn't work. However I still want to have the URLs look like /module/4711/ therefore I added this to my routes.rb:
map.connect '/module/:module_id', :controller => 'xpmodule', :action => 'index'
map.connect '/module/:module_id/subscribe', :controller => 'xpmodule',
:action => 'subscribe'
Now I want to test this controller with Rspec:
describe XpmoduleController do
fixtures :xpmodules
context "index" do
it "should assign the current xpmodule" do
xpm = mock_model(Xpmodule)
Xpmodule.should_receive(:find).and_return(xpm)
get "index"
assigns[:xpmodule].should be_an_instance_of(Xpmodule)
end
end
end
for which I get No route matches {:action=>"index", :controller=>"xpmodule"}. Which of course is sort-of right, but I don't want to add this route just for testing purposes. Is there a way to tell Rspec to call a different URL in get?

As far as I can tell you're testing controller action and not routing to that action. These are two different things!
Try this for starters:
it "should map xpmodules controller to /modules url" do
route_for(:controller => "xpmodule", :action => "index").should == "/modules"
end
Apply for other actions as well. If you want to do a reverse routing (from url to controller/action) then do this:
it "should route /modules url to xpmodules controller and index action" do
params_from(:get, "/modules").should == {:controller => "xpmodules", :action => "index"}
end

Head, meet wall, Wall, meet head. bang.
Not getting an answer on SO is a sure sign that I should try harder. Therefore I added the /xpmodule route explicitly to the routes.rb. Just to notice that the tests are still failing. To cut a long story short:
it "should assign the current xpmodule" do
xpm = mock_model(Xpmodule)
Xpmodule.should_receive(:find).and_return(xpm)
get "index", :module_id => 1
assigns[:xpmodule].should be_an_instance_of(Xpmodule)
end
is the solution.

Related

In Rails Controller testing, is there a way to pass query (non-routing) parameters?

I'm writing controller tests in Rails and RSpec, and it seems from reading the source code of ActionController::TestCase that it's not possible to pass arbitrary query parameters to the controller -- only routing parameters.
To work around this limitation, I am currently using with_routing:
with_routing do |routes|
# this nonsense is necessary because
# Rails controller testing does not
# pass on query params, only routing params
routes.draw do
get '/users/confirmation/:confirmation_token' => 'user_confirmations#show'
root :to => 'root#index'
end
get :show, 'confirmation_token' => CONFIRMATION_TOKEN
end
As you may be able to guess, I am testing a custom Confirmations controller for Devise. This means I am jacking into an existing API and do not have the option to change how the real mapping in config/routes.rb is done.
Is there a neater way to do this? A supported way for get to pass query parameters?
EDIT: There is something else going on. I created a minimal example in https://github.com/clacke/so_13866283 :
spec/controllers/receive_query_param_controller_spec.rb
describe ReceiveQueryParamController do
describe '#please' do
it 'receives query param, sets #my_param' do
get :please, :my_param => 'test_value'
assigns(:my_param).should eq 'test_value'
end
end
end
app/controllers/receive_query_param_controller.rb
class ReceiveQueryParamController < ApplicationController
def please
#my_param = params[:my_param]
end
end
config/routes.rb
So13866283::Application.routes.draw do
get '/receive_query_param/please' => 'receive_query_param#please'
end
This test passes, so I suppose it is Devise that does something funky with the routing.
EDIT:
Pinned down where in Devise routes are defined, and updated my example app to match it.
So13866283::Application.routes.draw do
resource :receive_query_param, :only => [:show],
:controller => "receive_query_param"
end
... and spec and controller updated accordingly to use #show. The test still passes, i.e. params[:my_param] is populated by get :show, :my_param => 'blah'. So, still a mystery why this does not happen in my real app.
Controller tests don't route. You are unit-testing the controller--routing is outside its scope.
A typical controller spec example tests an action:
describe MyController do
it "is successful" do
get :index
response.status.should == 200
end
end
You set up the test context by passing parameters to get, e.g.:
get :show, :id => 1
You can pass query parameters in that hash.
If you do want to test routing, you can write routing specs, or request (integration) specs.
Are you sure there isn't something else going on? I have a Rails 3.0.x project and am passing parameters.. well.. this is a post.. maybe it's different for get, but that seems odd..
before { post :contact_us, :contact_us => {:email => 'joe#example.com',
:category => 'Category', :subject => 'Subject', :message => 'Message'} }
The above is definitely being used in my controller in the params object.
I am doing this now:
#request.env['QUERY_STRING'] = "confirmation_token=" # otherwise it's ignored
get :show, :confirmation_token => CONFIRMATION_TOKEN
... but it looks hacky.
If someone could show me a neat and official way to do this, I would be delighted. Judging from what I've seen in the source code of #get and everything it calls, there doesn't seem to be any other way, but I'm hoping I overlooked something.

How to route a nested resource route to another?

I'm working on implementing boards.
Now I have BoardsController and PostsController.
By default, posts are nested by boards.
I want all board's post list have their special route using same PostsController
so I did this in route.rb
resources :notice, :controller => "posts", :board_id => 1
resources :faq, :controller => "posts", :board_id => 2
resources :qna, :controller => "posts", :board_id => 3
At first, it seems to work. But I realized a problem.
because i used same 'PostsController' in these resources.
Codes related to path are same when doing controller's action
like,
posts_controller
def create
#post = Board.find(params[:board_id]).posts.build(params[:post])
if #post.save
redirect_to board_posts_path(#post.board_id)
else
render 'new'
end
end
when I go to localhost:3000/notice/new, it works fine
but when I submitted the new post, controller redirects to localhost:3000/boards/1/posts/
because of redirect_to board_posts_path(#post.board_id)
and that's not what I want.
I could handle this using if statements, but it seems messy.
Is there any proper solution to this?
You can use the self.send on the controller to dynamically resolve the path by the post type. Assuming you have the type of the created post in a string ( I didn't understand from your question if Notice < Post and if you use Single Table Inheritance):
post_type = # Get the specific post type ( "notice, faq ...")
redirect_to self.send("#{post_type}_path", #post.board_id)

How do I test that a before_filter will redirect for all Rails controller actions?

I have a fairly typical require_no_user as a before_filter in one of my controllers. I need to test that a logged in user is redirected by this filter if they try to access any of the controller's actions.
Is there a sensible way to do this without enumerating all of the controller's actions in my test case?
I'm trying to avoid:
context 'An authenticated user' do
setup do
activate_authlogic
#user = Factory(:user)
UserSession.create(#user)
do
should 'not be allowed to GET :new' do
get :new
assert_redirected_to(root_path)
end
should 'not be allowed to POST :create' do
# same as above
end
# Repeat for every controller action
end
Not that I'm aware of... though you could make it a bit shorter by packing all the methods and actions into a hash:
should "be redirected" do
{
:get => :new,
:post => :create,
}.each do |method, action|
send(method, action)
assert_redirected_to(root_path)
end
end
Edit: so yeah, this is probably overkill, but here's another way:
should "be redirected" do
ActionController::Routing::Routes.named_routes.routes.each do |name, route|
if route.requirements[:controller] == #controller.controller_name
send(route.conditions[:method], route.requirements[:action])
assert_redirected_to(root_path)
end
end
end
Seems though that if you define multiple :methods in custom routes that it still only "finds" the first, e.g.
map.resources :foo, :collection => {
:bar => [:get, :post]
}
The above route will only be attempted with the GET verb.
Also if there are other requirements in the URL, such as presence of a record ID, my naive example ignores that requirement. I leave that up to you to hack out :)

How do I force a namespace for a helper from within a spec

I am trying to test the output of a view helper that resides within a namespace. The original helper is located under app/helpers/admin/events_helper.rb. The test is at spec/helpers/admin/events_helper_spec.rb and looks like this (simplified):
require File.dirname(__FILE__) + '/../../spec_helper'
describe Admin::EventsHelper do
fixtures :events, :users
before(:each) do
#event = events(:one)
#user = users(:one)
end
it "should include link to admin page for user" do
html = helper.event_message(#event)
html.should have_selector("a", :href => admin_user_path(#user))
end
end
The helper, ridiculously simplified, looks like this:
module Admin::EventsHelper
def event_message(event)
link_to(
event.message,
:controller => 'users', :action => 'show', :id => event.user.id)
end
end
When the event_message method is called from a controller within the Admin namespace, it renders the link as '/admin/users/:id' as intended. However, called from the spec, it renders as '/users/:id', making the test fail.
How do I specify the correct namespace to use for the helper within the spec?
Thanks!
I think the problem stems from the way Rspec (and the Rails test framework) handles controllers. For complex reasons (OK, reasons I don't understand), you don't get a real ActionController when testing, instead you get an instance of ActionView::TestCase::TestController. When using namespaces, the test controller in this case is not correctly inferring the actual controller path, so it guesses "/users", which is wrong.
Long story short, while there is probably a better way to do it, you can try stubbing out the controller method that gets called by url_for to generate the link:
it "should include link to admin page for user" do
controller.stub(:url_options).and_return(:host=>"test.host", :protocol=>"http://", :_path_segments=>{:controller=>"admin/users", :action=>"show"}, :script_name=>"")
html = helper.event_message(#event)
html.should have_selector("a", :href => admin_user_path(#user))
end
If i get your question correct, you are asking about specifying the controller namespace which is failing in spec right?
If you UsersController is within Admin namespace, then you should be doing this:
link_to(event.message, :controller => 'admin/users', :action => 'show', :id => event.user.id) in your helper method.
Note the value for controller key is admin/users

Is there a way to check the parameters and decide the routes based on the parameters in rails?

I am looking for a way to decide the routes based on a request parameter.For example i want to have route a request to web controller if it has params[:web] and to iPhone if it has params[:iphone]. Is it possible to do so keeping the names of the routes same but routing them to different controllers/actions depending upon the parameter?
Possible if you define route(or a named route) like below in your routes.rb file
map.connect '/:controller/:action/:platform',:controller => 'some controller name',:action=>'some action'
if you handle this in your action, you can use like params[:platform]
Read more on named routes if you customize more on this. As far as your prob is concerned I hope the above code solves the problem
Expanding on #lakshmanan's answer, you can do this:
Add this to your routes.rb:
map.named_route '/:controller/:action/:platform'
In your views,
<%= link_to "Blah blah", named_route_path(:controller => "some_controller",
:action => "some_action",
:platform => "some_platform")
In your some_controller,
def some_action
if params[:platform] == "web"
#DO SOMETHING
elsif params[:platform] == "iphone"
#DO SOMETHING
else
#DO SOMETHING
end
end
Assuming that there is a very good reason to have one controller accept this action (if there is shared code... move it to a helper method or model, and use the user agent info or named routes to your advantage), check the parameter and redirect to the appropriate controller and action:
def some_action
# some shared code here
if params[:platform] == 'iphone'
redirect_to :controller => 'foo', :action => 'bar'
elsif params[:platform] == 'web'
redirect_to :controller => 'baz', :action => 'baq'
else
# default controller and action here
end
end
If you really really want the named route to map to different controllers, you'll need to hardcode the platform string:
map.connect '/foo/bars/:id/iphone', :controller => 'iphone',:action=>'some_action'
map.connect '/foo/bars/:id/web', :controller => 'web',:action=>'some_action'
UPDATE0
From here, you might want to try map.with_options(:conditions => ... )

Resources