How to test the AdminController when it has no actions? - ruby-on-rails

My AdminController looks like:
class AdminController < ApplicationController
before_action :check_admin
private
def check_admin
redirect_to 'home/error' unless current_user.admin?
end
end
In my rspec test, how can I test this if there are no route or views?
require 'rails_helper'
RSpec.describe AdminController, type: :controller do
context "with no render_views" do
it "redirects for non-admin users" do
#???expect do
end
end
end

I am assuming that you are using a before_action in your AdminController, even though this controller does not have any actions, so that any controllers that inherit from it will automatically by "admin only".
If so, there are two ways to approach testing this.
1) Don't write a test for check_admin.
Instead, write tests for any controller actions that you define later! For example, if you have the following controller in your application tomorrow:
UsersController < AdminController
def index
#users = User.all
end
end
then you can write the following specs for that controller.
describe UsersController
it 'redirects for non-admins' do
# insert the test you feel like writing here!
end
it 'renders the right template for admin users' do
magical_login_method
get :index
expect(response).to render_template(:index)
end
end
and so on!
2) Call the private method directly
This approach makes me feel a bit icky. Although this defeats the philosophy of public vs private methods, you can call a private method in ruby by using the .send method.
describe AdminController
it 'redirects for non-admins' do
# make an instance of your controller
controller = AdminController.new
# expect the controller to call `redirect_to`
expect(controller).to receive(:redirect_to).with('home/error')
# call the private `check_admin` method
controller.send(:check_admin)
end
end
Some, perhaps many, would argue that this sort of testing is highly intrusive, and may even limit the flexibility of your codebase in the future. I'd recommend approach 1, not because it's lazy, but because it tests things once there's something to test!

Related

Anonymous controllers in Rails

I am currently learning to test with rspec (ruby on rails) (it's safe to assume I know very little), and am a bit stuck when it comes to testing with anonymous controllers. I tried googling it, but I feel like it doesn't give me what I'm looking for. So basically, what is an anonymous controller and why (or when) do I want one?
Thanks in advance
The main reason you would want to use an anonymous controller is that you are developing reusable library code (a gem) thats meant to plug into any arbitrary controller.
For example this base controller class to dry the common CRUD boilerplate code:
module MyGem
class ResourcefulController < ::ActionController::Base
before_action :set_resource, only: [:show, :edit, :update, :destroy]
def show
end
private
def derive_resource_class_name
self.class.name.demodulize.chomp('Controller').singularize
end
def resource_class
derive_resource_class_name.constantize
end
def set_resource
#resource = resource_class.find(params[:id])
end
end
end
The normal way of testing this would require that we create a controller and a route just for the test itself.
By using an anonymous controller we can get around that:
require 'rails_helper'
RSpec.describe MyGem::ResourcefulController, type: :controller do
controller do
def show
render plain: #resource
end
end
before do
model = Class.new do
def self.find(id)
"You found me"
end
end
stub_const("Resourceful", model)
end
it "derives the resource class from the name of the controller" do
get :show, params: { id: 1 }
expect(response.body).to eq "You found me"
end
end
RSpec also cleverly creates stub routes for the normal CRUD actions.
But for normal Rails application code its not very useful at all - parent classes (or included modules) are better tested by creating a request or feature spec that covers the actual behaviour in the end product. This lets you create a failing spec, get to green and then refactor.

Rails: how to write an anonymous spec controller to test authorisation

I have a controller, AdminController, which sets the various authorisation levels for the rest of the CMS. Because there are no controller actions, just methods, I began to research ways to test these against controllers.
The conclusion I came to was that they needed to be tested independently of the controllers they are used in (I want to to steer clear of integration testing if possible, like capybara etc).
I found some articles like this one to help me along.
So far I have written this spec which is failing with the errors below. I am not sure about it to be honest and wanted to here what SO community had to say on what I am trying to achieve.
describe AdminController do
controller do
before_filter :authorize_fixture_uploader!
def index
render text: 'Hello World'
end
end
let(:admin){FactoryGirl.create(:admin)}
describe "authentication" do
before do
sign_in admin
allow(controller).to receive(:current_admin).and_return(admin)
end
describe "authorize_fixture_uploader! helper" do
context "signed in" do
before do
allow(:admin).to receive(:authorize_fixture_uploader!).and_return(false)
get :index
end
it "redirects do admin_home_path" do
expect(response).to redirect_to admin_home_path
end
end
end
end
end
and here is the controller
class AdminController < ApplicationController
before_filter :authenticate_admin!
def authorize_fixture_uploader!
unless current_admin.fixture_uploader?
return redirect_to(admin_home_path)
end
end
end
This test is giving me the error
1) AdminController authentication authorize_fixture_uploader! helper signed in redirects do admin_home_path
Failure/Error: allow(:admin).to receive(:authorize_fixture_uploader?).and_return(false)
TypeError:
can't define singleton
I am worried its because my whole approach to this is wrong. Help would most certainly be appreciated.
Updated thanks to #blelump's answer.
I had a type which was causing the first issue. But Now I am getting error
undefined method `authorize_fixture_uploader?' for #<RSpec::Core::ExampleGroup::Nested_1::Nested_1::Nested_1::Nested_1:0x007f9357857108>
The logic behind this i throwing me a bit. How am I to test these methods independent of the controllers they are used?
You have a typo:
allow(:admin).to receive(:authorize_fixture_uploader!).and_return(false)
Now you're trying to add authorize_fixture_uploader! to Symbol. Just start with controller variable:
allow(controller).to receive(:authorize_fixture_uploader!).and_return(false)
Aside from the poor attention detail highlight by blelump above, the real flaw in my approach was the lack of routes. I found a very useful article from pivotal labs http://pivotallabs.com/adding-routes-for-tests-specs-with-rails-3/ which saved the day.
Read the article, but it essentially boils down to this.
require 'spec_helper'
class InheritsFromAdminController < AdminController
def show
render :text => "foo"
end
end
describe InheritsFromAdminController do
before do
Rails.application.routes.draw do
# add the route that you need in order to test
match '/foo' => "inherits_from_admin#show"
# re-drawing routes means that you lose any routes you defined in routes.rb
# so you have to add those back here if your controller references them
match '/login' => "sessions/new", :as => login
end
end
after do
# be sure to reload routes after the tests run, otherwise all your
# other controller specs will fail
Rails.application.reload_routes!
end
it "requires logged-in users" do
get :show
response.should redirect_to("/login")
end
end

Test controllers without going through routing

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?
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.
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

How to test application controller before filter methods in Rails 3?

I have a before_filter on my ApplicationController class and I want to write a test for it? Where should I write this test into? I do not want to go into every subclass controller test file and repeat the test about this filter.
Hence, what is the recommended way to test ApplicationController before_filters?
Note that I am using Rails 3.2.1 with minitest.
My case is slightly different than yours, but I needed to do something similar to test authentication across the site (with Devise). Here's how I did it:
# application_controller.rb
class ApplicationController < ActionController::Base
before_filter :authenticate_user!
end
# application_controller_test.rb
require 'test_helper'
class TestableController < ApplicationController
def show
render :text => 'rendered content here', :status => 200
end
end
class ApplicationControllerTest < ActionController::TestCase
tests TestableController
context "anonymous user" do
setup do
get :show
end
should redirect_to '/users/sign_in'
end
end
If there's specific controllers that need to skip the before filter I'll have a test to make sure they skip it in the specific controller's tests. This isn't quite your situation as I'm interested in the effect of the method, not just knowing it was invoked, but I thought I'd share in case you found it useful.
Improving on #bmaddy answser, you do need to setup routing for the specs to run.
Here is a rails 5 working example:
require 'test_helper'
class BaseController < ApplicationController
def index
render nothing: true
end
end
class BaseControllerTest < ActionDispatch::IntegrationTest
test 'redirects if user is not logedin' do
Rails.application.routes.draw do
get 'base' => 'base#index'
end
get '/base'
assert_equal 302, status
assert_redirected_to 'http://somewhere.com'
Rails.application.routes_reloader.reload!
end
test 'returns success if user is loggedin' do
Rails.application.routes.draw do
get 'base' => 'base#index'
end
mock_auth!
get '/base'
assert_equal 200, status
Rails.application.routes_reloader.reload!
end
end
I now believe that I have to have all my controllers tests test about the before_filter existence and that this filter works as expected. This is because, I cannot know whether a controller uses a skip_before_filter when it shouldn't.
Hence, I decided to use mock (#controller.expects(:before_filter_method)) to make sure that the filter is called. So, for example, in a index action I write in my test:
test "get index calls the before filter method" do
#controller.expects(:before_filter_method)
# fire
get :index
end
This will make sure that my controller calls before_filter_method on the particular action. I have to do this on all my actions tests.
If anyone else has a better solution, let me know.
Usually when I want something like this I just test the expected behaviour without taking into account that this particular behaviour may be implemented in a filter and not in a method per se. So for the following simple scenario :
class Controller < ApplicationController
before_filter :load_resource, :only => [:show, :edit]
def show
end
def edit
end
def index
end
#########
protected
#########
def load_resource
#resource = Model.find(params[:id])
end
end
I would simple test that #show and #edit assign the #resource thing. This works for simple scenarios pretty much ok. If the filter is applied to a lot of actions/controllers then you can extract the testing code and reuse it amongst the tests.

RSpec and protected methods, controller spec for current_user

I might be going at this the wrong way. I'm doing the spec first, BDD/TDD and hit a bump.
I have this application_controller_spec.rb
require "spec_helper"
describe ApplicationController do
describe "current_user" do
it "should return nil if no one is logged in" do
subject.current_user.should be_nil
end
it "should return currently logged in user" do
hash = {user_id: "my_id"}
subject.should_receive(:session).and_return hash
subject.current_user.should == "my_id"
end
end
end
which works perfectly fine without the protected keyword.
application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
helper_method :current_user
protected
def current_user
session[:user_id]
end
end
with the protected enabled, I get this error msg
NoMethodError: protected method `current_user' called for #<ApplicationController:0x2a90888>
I should be able to test using the helper_method... Any suggestions?
helper_method makes the method available in the views, not the controller, according to the docs.
If you really need to access the method from controller specs, you could use send:
subject.send(:current_user).should be_nil
But you might want to consider whether testing non-public methods makes sense, or if it would be better to test using view specs. Or whether the method needs to be protected in the first place. It might also be instructive to see how Devise and Authlogic implement testing for their current_user methods.
Although it's been quite some time since the original question, maybe someone finds this useful.
You can make an anonymous subclass of the ApplicationController and expose protected methods inside it. No need for send() method.
Here is how to do it:
describe ApplicationController, type: :controller do
controller do
def current_user
super
end
end
...
it 'should return nil if no one is logged in' do
expect(controller.current_user).to be_nil # (or whatever)
end
end
The source is this SO answer.

Resources