I have a dynamically driven Rails application where views and determined by the path requested.
routes.rb:
Rails.application.routes.draw do
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
get ':tenant', to: 'tenants#home'
get '/:path', to: 'tenants#page', :constraints => { :path => /.*/ }
end
TenantsController:
class TenantsController < ApplicationController
def home
render :template => params[:tenant] + "/home"
end
def page
render :template => params[:path]
end
end
As you can see what simply happens is we get the path constraint from the endpoint and render a view template from it.
I would like to write a test that ensures rails requests a view template that matches the URL requested. (Basically test the page method within TenantsController).
Given that I do not want to tie my test into tenants that may be in the system, how can I write a test for this generically without the test knowing about some Tennant?
You can write tests for your controller using the assert_template method:
test "should render the correct template" do
['path1', 'path2'].each do |path|
get '/', params: { path: path }
assert_template "#{path}"
end
end
This guide provides more details on how to test your controllers.
Related
I have created a rails project that has some code that I would like to execute as an API. I am using the rails-api gem.
The file is located in app/controllers/api/stats.rb.
I would like to be able to execute that script and return json output by visiting a link such as this - http://sampleapi.com/stats/?location=USA?state=Florida.
How should I configure my project so that when I visit that link it runs my code?
the file should be called stats_controller.rb app/controllers/api/stats_controller.rb
you can create an index method where you can add your code
class API::StatsController < ApplicationController
def index
#your code here
render json: your_result
end
end
in the file config/routes.rb you should add
get 'stats' => 'api/stats#index', as: 'stats'
To access the params in the url you can do it in your index method with params[:location] ,params[:state]
Here's how I would think of this:
in app/controllers/api/stats_controller.rb
module Api
class StatsController
def index
# your code implementation
# you can also fetch/filter your query strings here params[:location] or params[:state]
render json: result # dependent on if you have a view
end
end
end
in config/routes.rb
# the path option changes the path from `/api` to `/` so in this case instead of /api/stats you get /stats
namespace :api, path: '/', defaults: { format: :json } do
resources :stats, only: [:index] # or other actions that should be allowed here
end
Let me know if this works
I'm having the worst time rendering a .json.erb file from my controller while being able to test it with RSpec. I have api_docs/index.json.erb and the following controller:
class ApiDocsController < ApplicationController
respond_to :json
def index
render file: 'api_docs/index.json.erb', content_type: 'application/json'
end
end
The explicit render file line seems unnecessary, but if I don't do that or render template: 'api_docs/index.json.erb', then I get an error about "Missing template api_docs/index". Likewise if I do have to pass the file name, it sucks even more that I have to give the exact directory--Rails should know that my ApiDocsController templates live in the api_docs directory.
If I have render file or render template, then I can visit the page and get the JSON contents of my index.json.erb file, as expected. However, this RSpec test fails:
let(:get_index) { ->{ get :index } }
...
describe 'JSON response' do
subject {
get_index.call
JSON.parse(response.body)
}
it 'includes the API version' do
subject['apiVersion'].should_not be_nil
end
end
It fails on the JSON.parse(response.body) line and if I raise response.body, it's an empty string. If I do render json: {'apiVersion' => '1.0'}.to_json in the controller, then the test passes just fine.
So, how can I always render the JSON template when I go to /api_docs (without having to put .json at the end of the URL), and in a way that works both in the browser and in my RSpec test? And can I render the template without having to have some long render call in which I pass the full path of the view?
Actually since you're already using respond_to :json in your controller you can use just a render method to choose your template and, as you probably know, if the template have the same name of the controller method you should be able to suppress the whole render method.
If you just remove the render line, what's the result?
Part of my solution was based on this answer to another question: adding defaults: {format: :json} to my routes file lets me go to /api_docs and see the JSON when the action is just def index ; end with no render. The RSpec test still fails though. The full line from my routes file: resources :api_docs, only: [:index], defaults: {format: :json}.
Thanks to this guy with the same problem and his gist, I added render_views to my describe block and got my test to pass:
describe ApiDocsController do
render_views
...
let(:get_index) { ->{ get :index } }
describe 'JSON response' do
subject {
get_index.call
JSON.parse(response.body)
}
it 'includes the API version' do
subject['apiVersion'].should_not be_nil
end
end
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.
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
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.