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.
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 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
I have generated Devise's views running rails g devise:views and would now like to test them.
This is what I have come up with:
require 'spec_helper'
describe "devise/sessions/new" do
before do
render
end
it "renders the form to log in" do
rendered.should have_selector("form", action: user_session_path, method: :post) do |form|
end
end
end
For the render statement it gives me undefined local variable or method 'resource'. After googling around I found that I should add
#user.should_receive(:resource).and_return(User.new)
before the render statement - but it still gives me the same error, and I am not really sure how to use it.
What am I doing wrong? Thanks for your help.
Just another thought incase anyone runs into the same issue. I wasn't a huge fan of adding code to my helpers just to make it so tests can pass so I ended up adding this code in a before block in my tests:
before do
view.stub(:resource).and_return(User.new)
view.stub(:resource_name).and_return(:user)
view.stub(:devise_mapping).and_return(Devise.mappings[:user])
end
And whaddya know? I found this answer where someone had a similar problem. The solution is to include the following code in your application helper:
def resource_name
:user
end
def resource
#resource ||= User.new
end
def devise_mapping
#devise_mapping ||= Devise.mappings[:user]
end
This is necessary because devise is using certain helper methods in its controllers. If I access the views from my specs, however, these helper methods are not available, hence my tests fail. Putting these methods inside the application helper makes them accessible throughout the application, including my specs, and indeed the tests pass!
In rspec there's a configuration which doesn't allow you to stub out methods which are not defined. With rails 4 it's even standard behavior:
# spec/spec_helper.rb
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
Because of that the highest-voted answer doesn't work with devise helpers. Change verify_partial_doubles to false or use an actual helper.
I wanted to do #MikeFogg's solution - I do something similar in another view spec to deal with Pundit - but of course I ran up against the issue #ChrisEdwards pointed out with regards to needing mocks.verify_partial_doubles = false.
However, I was not much interested in turning that off across my whole test suite, it's a good check, "generally recommended" and default in Rspec 4+.
I actually first posted a solution here where I reconfigured RSpec in before/after blocks, but I ran across a really, really easy way to do this, and it works great:
before(:each) do
without_partial_double_verification do
allow(view).to receive(:resource).and_return(Student.new)
allow(view).to receive(:resource_name).and_return(:student)
allow(view).to receive(:devise_mapping).and_return(Devise.mappings[:student])
end
# other before_each configuration here as needed
end
This has been around since 2016 and was originally called without_verifying_partial_doubles, but was renamed to the current without_partial_double_verification. Does not appear to be documented anywhere I can see.
Like Mike Fogg, I didn't like the idea of adding several methods to my ApplicationController just to get these view specs working.
I noticed that my rspec installation had a spec/mixins directory, so I did the following:
# spec/mixins/devise_helpers.rb
module DeviseHelpers
def resource_name
:user
end
def resource
#resource ||= User.new
end
def devise_mapping
#devise_mapping ||= Devise.mappings[:user]
end
end
Then include in my spec:
# spec/views/devise/registrations/new.html.haml_spec.rb
require 'rails_helper'
include DeviseHelpers
describe 'devise/registrations/new.html.haml' do
it 'has a login link for existing users' do
render
expect(rendered).to have_link('Log in')
end
end
Now I can include mix those methods into any spec that needs them.
Combining #sixty4bit answer with this answer here I came up with this solution:
class DeviseHelpers < Module
def initialize(resource_name, resource_class)
#resource_name = resource_name
#resource_class = resource_class
end
def extended(base)
_resource_name = #resource_name
_resource_class = #resource_class
base.class_eval do
define_method :resource_name do
_resource_name
end
define_method :resource do
#resource ||= _resource_class.new
end
define_method :devise_mapping do
#devise_mapping ||= Devise.mappings[_resource_name]
end
end
end
end
This allows you to use it with different resources by requiring it at the top of your spec:
require 'support/devise_helpers'
and then calling it like this:
before do
view.extend(DeviseHelpers.new(:customer, Customer))
end
For RSpec 4, Rails 6 and Devise 4.7 I was unsuccessful with the other recommendations here or was wary of ramifications to my other testing or production code.
allow(view).to receive(:current_user).and_return(user)
in my view specs was what ultimately worked for me without any other configuration changes. (One has to define user first.)
It is not unlikely that there have been meaningful changes to the underlying gems/frameworks in recent years that invalidates some of the other solutions here. It is even more likely that I have misconfigured Devise, RSpec and/or related helpers.
I confirmed that current_user was populated in rspec/rails/example/view_example_group.rb, but was returning nil during render. I found this comment in view_example_group.rb and followed it:
# The instance of `ActionView::Base` that is used to render the template.
# Use this to stub methods _before_ calling `render`.
#
# describe "widgets/new.html.erb" do
# it "shows all the widgets" do
# view.stub(:foo) { "foo" }
# render
# # ...
# end
# end
def view
_view
end
https://github.com/rspec/rspec-rails/blob/main/lib/rspec/rails/example/view_example_group.rb
stub syntax is deprecated but view.stub(:current_user) { user } will also work (for now).
I'm using rspec, with devise and trying to test my views.
In a helper function which my view calls, I use the variable current_user
This works fine in practice, but using rspec, I get a sad 'undefinied variable or method current_user' error.
I've tried a lot of things that don't work.
#user = FactoryGirl.build_stubbed(:user)
assign(current_user, #user)
sign_in :user, #user
def current_user
#user
end
current_user = #user
view.stub!(:current_user).and_return #user
assign[:currrnet_user] = #user
Anyone know how to do this? Thanks in advance.
I had to do this to get it to work.
RSpec.describe 'foos/index.html.erb', type: :view do
before do
controller.singleton_class.class_eval do
def current_user
User.find_by(username: 'schmoe')
end
helper_method :current_user
end
render
end
...
end
So, the solution to helper methods is to stub them. I don't really like this solution, because I see it as a bug, but it is the way to do what needs to be done.
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.