Reputation: 77378
I'm trying to test my controller's action chain in isolation. Specifically, I want to ensure my desired behavior is applied to all my controller's actions. For example, test that all my actions require authentication:
context "when not authenticated" do
# single case
describe "GET index" do
it "responds with 401" do
get :index
response.code.should be(401)
end
end
# all of them...
described_class.action_methods.each do |action|
['get', 'put', 'post', 'delete', 'patch'].each do |verb|
describe "#{verb.upcase} #{action}" do
it "responds with 401" do
send verb, action
response.code.should == "401"
end
end
end
end
end
I expected this to work but it doesn't. I get some ActionController::RoutingErrors
. This is because some of my routes require params and in some cases I'm not supplying them (like when I call post :create
). I get that. But what I don't understand is: why should it matter!?
For these tests routing is a separate concern. I care about my action chains, not my requests (that's what I have routing specs
and request specs
for). I shouldn't need to concern myself with my route constraints at this level.
So my question: Is there a way to test just the action chain without simulating a request?
EDIT: some research
It looks like routes are being exercised in TestCase#process. Is this necessary?
Upvotes: 3
Views: 2495
Reputation: 77378
One work around is to loosen the routing engine's constraints. This doesn't bypass routing, but it does make it easier to work with for testing.
Add something like the following to your specs:
before(:all) do
Rails.application.routes.draw { match ':controller(/:action)' }
end
after(:all) do
Rails.application.reload_routes!
end
While not strictly an answer to the question, it might be a good enough work around.
Upvotes: 6
Reputation: 3627
I'd argue that routing is not a separate concern for controller specs. One reason why is that values are added to the params hash based on what values are passed into the url, and the code in your controller may depend on those values.
Anyway, I'm assuming that you have some kind of authorization method defined in your ApplicationController
. Testing each controller individually seems a little redundant. Here's how I'd do it:
require "spec_helper"
describe ApplicationController do
describe "require_current_user" do
ACTIONS_AND_VERBS = [
[:index, :get],
[:show, :get],
[:new, :get],
[:create, :post],
[:edit, :get],
[:update, :put],
[:destroy, :delete],
]
controller do
ACTIONS_AND_VERBS.each do |action, _|
define_method(action) do
end
end
end
ACTIONS_AND_VERBS.each do |action, verb|
describe "#{verb.to_s.upcase} '#{action}'" do
it "should be successful" do
send(verb, action, id: -1)
response.code.should eq("401")
end
end
end
end
end
And in my ApplicationController
I'd have something like...
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :require_current_user
def require_current_user
head :unauthorized
end
end
EDIT: If I understand correctly, what we're really testing is that your require_current_user
, or whatever equivalent authorization process you want to occur, is working as expected. In that case, we can test just one action, and trust that before_filter
works properly.
require "spec_helper"
describe ApplicationController do
describe "require_current_user" do
controller do
def index
end
end
it 'should head unauthorized for unauthorized users' do
get :index
response.code.should eq("401")
end
end
end
Upvotes: 2