RSpec and protected methods, controller spec for current_user - ruby-on-rails

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.

Related

How to test the AdminController when it has no actions?

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!

rspec controller test error undefined method for nil:NilClass

I'm new to Rspec and I am trying to get into the whole BDD mindset, so I'm pretty stumped about this error. I have have rails engine that I am trying to test. Here is the bulletin controller. Basically before any action I want to populate the list of courses.
class BulletinsController < ApplicationController
before_filter :get_courses
def new
#bulletin = Bulletin.new(author_id: #user.id)
end
...
private
def get_courses
if #user.has_role? :admin
#course_list = Course.all.sort_by(&:start_date)
...
end
end
The application controller has some methods that I want run on each request. I am using devise in the host app so I have access to the current_user method
class ApplicationController < ::ApplicationController
before_filter :get_user
...
def get_user
#user = current_user
end
...
end
And here is the spec I am trying to run:
describe BulletinsController do
routes { MyEngine::Engine.routes }
before { controller.stub(:authenticate_user!).and_return true }
before { controller.stub(:get_user).and_return (#user = create(:user)) }
describe "GET #new" do
it "assigns a new bulletin to #bulletin" do
bulletin = create(:bulletin)
controller.stub(:get_courses)
get :new
assigns(:bulletin).should eq(bulletin)
end
end
end
When I try to run the spec, I get the error:
NoMethodError: undefined method 'id' for nil:NilClass
I understand that I am getting this because #user is not defined when it is called in the bulletin building; however I thought that the before block in the spec would define the #user variable after stubbing out the :get_user filter. When I test the factories in the console, everything seems to be created with the proper associations (bulletin -> author, bulletin -> course, etc).
I'm not sure what I'm missing as to why the #user variable is not being carried through to my controller code. Any insight and/or good tutorials for rspec would be greatly appreciated.
Trying to stub out the methods that Devise could be using will be quite difficult unless you understand how Devise works.
The recommend way to test is to simply sign in the user using Devise test helper as per their documentation:
https://github.com/plataformatec/devise#test-helpers
describe BulletinsController do
routes { MyEngine::Engine.routes }
before { sign_in(user) }
let!(:user) { create(:user) }
describe "GET #new" do
it "assigns a new bulletin to #bulletin" do
bulletin = create(:bulletin)
controller.stub(:get_courses)
get :new
assigns(:bulletin).should eq(bulletin)
end
end
end
This way, you won't have to care about Devise methods and stubbing it. Just focus on testing your own method. :)
I guess You also need to stub current_user and it will be enough (no need to stub get_user):
before { controller.stub(:current_user).and_return (#user = create(:user)) }
And i guess the good practice is to let user (if you need it more than once):
routes { MyEngine::Engine.routes }
let!(:user) { create(:user) }
before { controller.stub(:current_user).and_return user }
If you need an access to private methods, you can try something like this:
subject.send(:current_user=, user)
Could be a controller instead of subject, not sure what version which supports.
Update. Actually, it's really tricky to test private methods. I checked that current_user in devise defines like:
def current_#{mapping}
#current_#{mapping} ||= warden.authenticate(scope: :#{mapping})
end
So, you can try stub warden.authenticate to returns user:
allow_any_instance_of(Warden).to receive(:authenticate).and_return(create(:user))

testing a current_user in application_controller with Rspec

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

How to stub ApplicationController method in request spec

I am needing to stub the response of a current_user method in an Rspec/capybara request spec. The method is defined in ApplicationController and is using helper_method. The method should simply return a user id. Within the test, I'd like this method to return the same user id each time.
Alternatively, I could fix my problem by setting session[:user_id] in the spec (which is what current_user returns)... but that doesn't seem to work either.
Are either of these possible?
Edit:
Here is what I've got (it is not working. It just runs the normal current_user method).
require 'spec_helper'
describe "Login" do
before(:each) do
ApplicationController.stub(:current_user).and_return(User.first)
end
it "logs in" do
visit '/'
page.should have_content("Hey there user!")
end
end
Also not working:
require 'spec_helper'
describe "Login" do
before(:each) do
#mock_controller = mock("ApplicationController")
#mock_controller.stub(:current_user).and_return(User.first)
end
it "logs in" do
visit '/'
page.should have_content("Hey there user!")
end
end
skalee seems to have provided the correct answer in the comment.
If the method you're trying to stub is an instance method (most likely) and not a class method then you need use:
ApplicationController.any_instance.stub(:current_user)
Here are a couple of examples of the basic form.
controller.stub(:action_name).and_raise([some error])
controller.stub(:action_name).and_return([some value])
In your particular case, I believe the proper form would be:
controller.stub(:current_user).and_return([your user object/id])
Here's a full working example from a project I work on:
describe PortalsController do
it "if an ActionController::InvalidAuthenticityToken is raised the user should be redirected to login" do
controller.stub(:index).and_raise(ActionController::InvalidAuthenticityToken)
get :index
flash[:notice].should eql("Your session has expired.")
response.should redirect_to(portals_path)
end
end
To explain my full example, basically what this does is verify that, when an ActionController::InvalidAuthenticityToken error is raised anywhere in the app, that a flash message appears, and the user is redirected to the portals_controller#index action. You can use these forms to stub out and return specific values, test an instance of a given error being raised, etc. There are several .stub(:action_name).and_[do_something_interesting]() methods available to you.
Update (after you added your code): per my comment, change your code so it reads:
require 'spec_helper'
describe "Login" do
before(:each) do
#mock_controller = mock("ApplicationController")
#mock_controller.stub(:current_user).and_return(User.first)
end
it "logs in" do
visit '/'
page.should have_content("Hey there user!")
end
end
This works for me and gives me a #current_user variable to use in tests.
I have a helper that looks like this:
def bypass_authentication
current_user = FactoryGirl.create(:user)
ApplicationController.send(:alias_method, :old_current_user, :current_user)
ApplicationController.send(:define_method, :current_user) do
current_user
end
#current_user = current_user
end
def restore_authentication
ApplicationController.send(:alias_method, :current_user, :old_current_user)
end
And then in my request specs, I call:
before(:each){bypass_authentication}
after(:each){restore_authentication}
For anyone else who happens to need to stub an application controller method that sets an ivar (and was stymied by endless wanking about why you shouldn't do that) here's a way that works, with the flavour of Rspec circa October 2013.
before(:each) do
campaign = Campaign.create!
ApplicationController.any_instance.stub(:load_campaign_singleton)
controller.instance_eval{#campaign = campaign}
#campaign = campaign
end
it stubs the method to do nothing, and sets the ivar on rspec's controller instance, and makes it available to the test as #campaign.
For Rspec 3+ the new api is:
For a controller test, nice and short:
allow(controller).to receive(:current_user).and_return(#user)
Or for all instances of ApplicationController:
allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(#user)
None of the provided responses worked for me. As in #matt-fordam's original post, I have a request spec, not a controller spec. The test just renders the view without launching a controller.
I resolved this by stubbing the method on the view as described in this other SO post
view.stub(:current_user).and_return(etc)

Functional testing Authlogic?

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.

Resources