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))
Related
I'm writing a controller test, and I wanted to verify Controller1's call_dashboard_function.
class Controller1 < ApplicationController
before_action :init_dashb
def call_dashboard_function
#dashb.do_something!
end
def init_dashb
#dashb ||= Dashboard.new(current_user)
end
end
class Dashboard
#... this is a facade class
end
Actual test:
test "should do something" do
sign_in #user
patch :call_dashboard_function
end
The problem is I don't want to test the dashboard class, since I have a separated test for it. So I'd like to mock, stub, etc this class behavior
That would be easy if I had access from outside. But I can't see dashboard class from outside the controller.
you should stub it:
let(user) { double('user') } # if you don't need real user
# or
let(user) { create(:user) } # if you need real user
before { allow_any_instance_of(Dashboard).to receive(:current_user) { user } }
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!
I've set up a controller I'm testing so that most requests to its actions redirect to the sign_in page if a session doesn't exist.
Because of this, I need to use the sign_in method when testing controllers in a unit test. I also need to create the user in the database so they can sign in.
This is all easily achieved like this:
describe MyController, do
let(:user) { FactoryGirl.create(:user)} # The 'create' creates in the database as well as in memory
context "with session" do
before {
sign_in user
}
context ".index" do
assigns(:example).should == "Just an example"
end
end
end
However, this isn't a good unit test because it's depending on active record and the database, as well as Devise's test helper methods.
So how can I use a mock (or something) to stop my controller redirecting me when I'm trying to test it?
My controller:
class MyController < ApplicationController
before_filter :authenticate_user!, only: [:index]
def index
#example = "Just an example"
end
end
Stub user authentication as advised here in the Devise Wiki.
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.