Rails 3: How to test router scope? - ruby-on-rails

I'm using Rails 3.0 and I have the following in my routes.rb files:
scope "/:location" do
resources :agents
end
So I can do this:
agents = Agent.where("location = ?", params[:location])
(There might be a better way to do this..? Mostly I want URLs like: myurl.com/london/agents)
Anyway, my problem is with the tests (which I'm still learning), how can I make this work with the scope:
class AgentsControllerTest < ActionController::TestCase
test "should get index" do
get 'index'
assert_response :success
end
end
It just gets a route no found error.
Any help would be great, thanks.

Because you have scoped your resources, you don't actually have direct access to the agents_controller via any actions. So calling get 'index' is really just trying to access /agents. If you tried this in your browser, it would fail as well.
To get a success you'll have to supply a location param like so:
get 'index', { :location => 'hawaii' }
Hope that helps.
Edit: If you want additional access to the agents_controller (meaning location is optional) simply add a top-level resource for it in your routes file:
resources :agents

Related

What's the expected format of a URL when sending a request from a controller test?

I have a route that looks like this in my Rails app:
Rails.application.routes.draw do
scope module: "api" do
namespace :v1 do
# snip
post "my_route", to: "my_controller#my_action"
I'm trying to write a controller test for this action:
RSpec.describe Api::V1::MyController, type: :controller do
describe "POST my_route" do
it "should respond with a 200 status" do
post "api/v1/my_route"
expect(response.status).to eq(200)
end
end
end
When I do this, my test fails with a ActionController::UrlGenerationError error.
What string should I use in my call to post so that RSpec correctly matches my route when it simulates the request? (Notice that my controller lives in the Api::V1 module; I'm not sure whether this makes a difference or not.)
I've tried:
"api/v1/my_route"
"/api/v1/my_route"
"v1/my_route"
"/v1/my_route"
"my_route"
"/my_route"
I get the same error with all of these strings, and I'm not sure what else could possibly be expected.
There are many other questions about UrlGenerationError in RSpec tests. None of them have helped me because they all seem to use built-in Rails routes, like :index or :create. I've specified my action and route directly, so I can't rely on quite as much Rails magic.
I believe that if I knew which format RSpec was expecting me to give for the path string I pass to post, I'd be able to figure this out very quickly. Unfortunately, I've had a hard time finding the relevant docs. It seems like most of RSpec's documentation is based on showing example tests, and again, since I'm not using much Rails magic, their examples don't show me what I'm supposed to be doing. What is the format for the URL string I'm supposed to use? Can you please point me to the relevant docs?
Controller RSpec paths don't expect you to use the routes specified in config/routes.rb when simulating requests. Instead, use the method names on the controllers themselves.
In my example, I have this route:
Rails.application.routes.draw do
scope module: "api" do
namespace :v1 do
# snip
post "my_route", to: "my_controller#my_action"
And this controller:
module Api
module V1
class MyController
def my_action
# snip
So the name of the route is my_route, and the name of the controller method is my_action. In writing the corresponding test case, I need to refer to my_action, the method, not my_route, the route.
Like so:
RSpec.describe Api::V1::MyController, type: :controller do
describe "POST my_route" do
it "should respond with a 200 status" do
post "my_action"
expect(response.status).to eq(200)
end
end
end
(Although I found this unintuitive at first, it does make sense now that I think about it. It decouples the controller tests from the routes configuration, making the tests less brittle.)

ActiveRecord::RecordNotFound for an object, apparent routing bug

I am a Rails newbie and am struggling with appears to be a routing bug.
I have a Site object that holds information about a website and how to crawl it. I want to test my code's connection to the site. Clicking "test site" in my app creates an error:
Couldn't find Site without an ID
app/controllers/sites_controller.rb:86:in `test_site'
routes.rb:
...
post "/test_site" => "sites#test_site"
get "home/index"
resources :sites, :logins
...
index.html.erb
...
<% #sites.each do |site| %>
<tr>
<td>
<%= form_tag test_site_path(site) do -%>
<div><%= submit_tag 'Test site' %></div>
<% end -%>
...
sites_controller.rb
...
def test_site
#site = Site.find(params[:id])
...
It looks like the Sites controller is not getting a Site :id from test_site_path(site). I'm not sure how to setup routes and pass the ID correctly.
Thanks!
Edit: I tried adding this code to my routes.rb:
resources :sites do
get "/test_site", :action => "test_site", :on => :member
end
I get this error:
No route matches {:controller=>"sites", :action=>"test_site", :format=>#<Site id: 11, ...
What might I be doing wrong?
For a newbie the best thing I can tell you to do is to get in the process of writing a controller test when you are adding a new feature as it helps debug issue like this. Use restful actions instead of creating your own. Try to use index, new, edit, create, update, show, destroy instead.
sites_controller_spec.rb
require 'spec_helper'
describe SitesController do
let(:site) { Site.create() } #Add to create what a site requires, eventually this will become a fixture but don't worry about that now
describe "#show" do
before { get :show, id: site.to_param }
it "responds with success" do
response.should be_success
end
end
end
Run this test. It will tell you that you have no route
routes.rb
resources :sites, only: %w[show]
Run this test. It will fail and tell you to create a sites controller and a show action:
class SitesController < ApplicationController
def show
end
end
Run the test again. It will now complain because there is no view.
View -> create directory (sites) -> inside create a file show.html.erb
Run the test again. It now is passing. Your routing is now working correctly, using REST, and checking that it is working is now automated.
This may seem a bit imitating right now, but I guarantee that if you make this a habit it will become second nature and you won't have to deal with routing bugs again.

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 test nested resources

I have a game model that has some releases (has_many). I've chosen to use nested resources to express this.
resources :games do
resources :releases, only: [:new, :create, :destroy]
end
I'm trying to use RSpec to test my release controller actions. Here is a part of my test file.
describe "GET new" do
it "assigns a new release as #release" do
get :new, {}, valid_session
assigns(:release).should be_a_new(Release)
end
end
When I don't set game_id parameter I have a No Routes Match error. And when I add :game_id => 1 I have a RecordNotFound error.
So my question is how can I set a game object to express the nested resource ?
I'm really new to rspec too - be gentle :) Once you get into testing your apps properly, your life will be faster, better and less stressful. Your applications will benefit even more and so will your customers.
After a month or so, I don't use the browser until I need to style my pages. Faster and more efficient.
Personally, I started reading through this tutorial:
http://everydayrails.com/2012/04/07/testing-series-rspec-controllers.html
And also, as ever, the following RailsCasts helped enormously:
http://asciicasts.com/episodes/275-how-i-test
http://asciicasts.com/episodes/158-factories-not-fixtures
In your case, try something like this (without FactoryGirl).
describe "GET new" do
it "assigns a new release as #release" do
game = Game.create!( << INSERT VALID ATTRIBUTE >> )
get :new, {:game_id => game.id}, valid_session
assigns(:release).should be_a_new(Release)
end
end
I think that's right, haven't tested myself. There's a few other ways you could do it including put the game creation method is a before filter:
before(:each) do
#game = Game.create!( << INSERT VALID ATTRIBUTE >> )
end
Or, like I said, using Factory Girl to build the model for you.
This is how we do it.

Why is this controller test on a create action failing?

I'm getting a failing test here that I'm having trouble understanding. I'm using Test::Unit with Shoulda enhancement. Action in users_controller.rb I'm trying to test...
def create
unless params[:user][:email] =~ / specific regex needed for this app /i
# ...
render :template => 'sessions/new'
end
end
Test...
context 'on CREATE to :user' do
context 'with invalid email' do
setup { post :create, { 'user[email]' => 'abc#abcd' } }
should_respond_with :success
end
# ...
end
Fails because "response to be a <:success>, but was <302>". How is it 302?
Change action to...
def create
render :template => 'sessions/new'
end
Test still fails.
#Ola: You're wrong: POST is connected to create. PUT is normally connected to update.
A :forbidden is quiet odd though. Here are some suggestions to find the problem (I've never used Shoulda, but I don't think it is a problem with Shoulda.
Make sure the route is defined in config/routes.rb and check with rake routes
Do you have any before_filters that could be responsible for that behaviour (login filter, acts_as_authenticated etc..)? Checkout log/test.log. A halt in the filter chain shows up there.
Print out the response body puts response.body to see what you get returned.
Hope this helps.
If you're using default REST-ful URLs, you probably should use PUT, not POST... Since PUT is connected to create, POST to that URL will give you an unauthorized and redirect.

Resources