I have a standard current_user in application_controller like this:
describe ApplicationController < ActionController::Base
helper_method :current_user
def current_user
#current_user ||= User.find_by_auth_token(session[:auth_token]) if session[:auth_token]
end
I'm curious as to how or whether if this should be tested. I was thinking like (this works but I'm not sure if there is a better way or whether even necessary):
describe 'current_user' do
it 'should return a valid_current' do
user=FactoryGirl.create(:user)
user.auth_token='abc123'
user.save
request.session['auth_token']='abc123'
returned_user=controller.send(:current_user) # not sure about this
returned_user.id.should eq(user.id)
end
end
but not really sure? I'm testing our auth in other places for our api but curious (1) how or (2) whether this should be tested (even for completeness)?
thx
Related
Even a seemingly simple index action feels incredibly complicated to test in isolation.
I find myself having to mock out several of my User and Tenant methods just to get through the before_filters. Then I need to mock out Kaminari and Tenant#users for the action.
This feels excessive for testing a controller action with no control flow.
TDD principle would say that an excessive need for mocking is a sign of poor design, but then I'm not sure how I would extract this functionality into a domain object.
Is this sort of painful mocking standard for testing Rails controllers? Is there better way to do this that I'm simply not aware of?
For instance, perhaps skipping before_filters would make this less painful, but as they are consequential private methods, I feel that skipping them is missing the point.
class UsersController < AdminController
before_filter :check_auth
before_filter :check_admin
around_filter :set_tenant_time_zone, if: current_tenant
def index
Kaminari.paginate(current_tenant.users).page(params[:page])
end
private
def current_user
# gets user from session
end
def current_tenant
current_user.tenant if current_user
end
def set_tenant_time_zone
Time.use_zone(current_tenant.time_zone, &block)
end
def check_auth
redirect_to login_url unless AuthChecker.new(current_user, request.remote_ip).has_access?
end
def check_admin
redirect_to root_url unless current_user.is_admin?
end
end
You have to do all those mocks/stubs if you want to run those before_filters but I think, that, for those cases, is better to use some spec helper method to create a logged in user so, on your spec, you only need to call that method on a "before(:each)" block of your controller where you want a user.
In spec_helper.rb:
def current_user(stubs = {})
unless #current_user
u = FactoryGirl.build(:user, stubs)
u.save(:validate => false)
#current_user = u
end
#current_user
end
def current_user_session(stubs = {}, user_stubs = {})
#current_session ||= mock_model("Session", {:record => nil, :user => current_user(user_stubs)}.merge(stubs))
end
def login(session_stubs = {}, user_stubs = {})
UserSession.stub(:find).and_return(current_user_session(session_stubs, user_stubs))
controller.stub(:current_user => #current_user)
end
so, on the controller specs that require a logged in user with some special stub I can do
describe 'GET index' do
before(:each) do
login #this does all you need to pass the filters
end
it 'does something' do
current_user.stub(:some_method)
get :index
expect(response).to something
end
end
that way the test only has stubs, instances and expectations for the actual code of the action and not the filters
I have the following application_controller method:
def current_account
#current_account ||= Account.find_by_subdomain(request.subdomain)
end
Should I be calling it using a before_filter or a helper_method? What's the difference between the two and what should I consider in terms of the trade-offs in this case?
Thanks.
UPDATE FOR BETTER CLARITY
I'm finding that I can user the before_filter instead of the helper_method in that I'm able to call controller defined methods from my views. Perhaps it's something in how I arranged my code, so here is what I have:
controllers/application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
include SessionsHelper
before_filter :current_account
helper_method :current_user
end
helpers/sessions_helper.rb
module SessionsHelper
private
def current_account
#current_account ||= Account.find_by_subdomain(request.subdomain)
end
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def logged_in?
if current_user
return true
else
return false
end
end
end
controllers/spaces_controller.rb
class SpacesController < ApplicationController
def home
unless logged_in?
redirect_to login_path
end
end
end
views/spaces/home.html.erb
<%= current_account.inspect %>
In theory, this shouldn't work, right?
There is no relationship between using before_filter or helper_method. You should use helper method when you have a method in your controller that you would like to reuse in your views, this current_account might be a nice example for helper_method if you need to use it in your views.
They are two very different things. A before_filter is something that you want to be called once before an action starts. A helper method on the other hand gets repeated often, typically in a view.
That method you have there is just fine to stay where it is.
I solved my problem. I'm new to Rails, and didn't know that methods defined in the helpers directory are automatically helper_methods. Now I'm wondering how this effects memory/performance. But at least I have the mystery solved. Thanks everyone for your help!
I'm trying to set the current user into a variable to display "Logged in as Joe" on every page. Not really sure where to begin...
Any quick tips? Specifically, what file should something like this go in...
My current user can be defined as (I think): User.find_by_id(session[:user_id])
TY :)
You might want to use something like Authlogic or Devise to handle this rather than rolling your own auth system, especially when you aren't very familiar with the design patterns common in Rails applications.
That said, if you want to do what you're asking in the question, you should probably define a method in your ApplicationController like so:
def current_user
#current_user ||= User.limit(1).where('id = ?', session[:user_id])
end
You inherit from your ApplicationController on all of your regular controllers, so they all have access to the current_user method. Also, you might want access to the method as a helper in your views. Rails takes care of you with that too (also in your ApplicationController):
helper_method :current_user
def current_user ...
Note: If you use the find_by_x methods they will raise an ActiveRecord::RecordNotFound error if nothing is returned. You probably don't want that, but you might want something to prevent non-users from accessing user only resources, and again, Rails has you covered:
class ApplicationController < ActionController::Base
protect_from_forgery
helper_method :current_user
before_filter :require_user
private
def current_user
#current_user ||= User.limit(1).where('id = ?', session[:user_id])
end
def require_user
unless current_user
flash[:notice] = "You must be logged in to access this page"
redirect_to new_session_url
return false
end
end
end
Cheers!
It belongs in your controllers.
All your controllers inheirit from Application Controller for exactly this reason. Create a method in your Application Controller that returns whatever you need and then you can access it in any of your other controllers.
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.
In a few of my controllers I have a before_filter that checks if a user is logged in? for CRUD actions.
application.rb
def logged_in?
unless current_user
redirect_to root_path
end
end
private
def current_user_session
return #current_user_session if defined?(#current_user_session)
#current_user_session = UserSession.find
end
def current_user
return #current_user if defined?(#current_user)
#current_user = current_user_session && current_user_session.record
end
But now my functional tests fail because its redirecting to root. So I need a way to simulate that a session has been created but nothing I've tried has worked. Heres what I have right now and the tests pretty much ignore it:
test_helper.rb
class ActionController::TestCase
setup :activate_authlogic
end
posts_controller_test.rb
class PostsControllerTest < ActionController::TestCase
setup do
UserSession.create(:username => "dmix", :password => "12345")
end
test "should get new" do
get :new
assert_response :success
end
Am I missing something?
You should pass ActiveRecord object in UserSession.create
Something like:
u = users(:dmix)
UserSession.create(u)
http://rdoc.info/github/binarylogic/authlogic/master/Authlogic/TestCase
First you need to activate AuthLogic so that you can use it in your tests.
setup :activate_authlogic
Then you need a valid user record as Anton Mironov pointed out.
All I do in my rspec tests for my controller is create a User with Machinist and then assign that user to be the current_user.
def login_user(options = {})
user = User.make(options)
#controller.stub!(:current_user).and_return(user)
end
and this attaches the current_user to the controller, which would mean that your logged_in? method would work in your tests.
You obviously would probably need to adapt this to work in Test::Unit, and without Machinist if you don't use it, as I use rspec, but I'm sure the principle is the same.
Put this in test_helper.rb if you want all your tests to setup Authlogic:
class ActionController::TestCase
def self.inherited(subclass)
subclass.instance_eval do
setup :activate_authlogic
end
end
end
Here is a link to the AuthLogic test documentation. It's an important one but is a bit buried (the same link Simone posted, however his didn't work anymore).
That page has all the information you need to get going testing you application using AuthLogic for authentication.
Additionally, as railsninja suggested, use factories not fixtures. Take a look at factory_girl and machinist; pick your poison, they are both good.