I am currently trying to make a dropbox-esque application from a tutorial and I am having trouble trying to figure out how I would test this controller.
this is the AssetsController page
def index
#assets = current_user.assets
end
def show
#assets = current_user.assets.find(params[:id])
Also, assets belong_to :user and user has_many :assets
How would I go about putting that into an rspec test?
Firstly, be very careful about AssetsController.
Assuming you are using Rails 3, the "assets_path" is also the path used to load your application assets, and hence, anything you write to the session in that controller will be ignored silently. Probably not what you want! I'd strongly consider renaming the controller.
I'd first create the user in a sign-in block
module ControllerMacros
def login_user
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
#user = Factory.create(:user)
sign_in #user
end
end
end
You can then load this in the spec_helper.rb file
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
config.extend ControllerMacros, :type => :controller
end
Finally, you can use this in the test
describe MyController do
context "#index" do
login_user
before(:each) do
#assets = []
5.times{ #assets << Factory.create(:asset, :user => #user)}
end
it "should test index" do
get :index
assigns(:assets).should eq(#assets)
end
end
end
Now, that should test your asset list correctly.
EDIT: Just realised, I'm using FactoryGirl/Devise here, you may or may not be!
What exactly is you question? How to setup basic tests for the relations or how to test the management of current_user?
The basic tests for this should be in the Model Specs, since it's the job of the model to sort out such things.
I normally test it like this:
1) define the fixtures (or alternately use something like FactoryGirl) for two users and some assets (three for this example). assets for user a are named asset_a_*, those for user b asset_b_*)
2) The test is simply like this:
users(:users_a).assets.should have(3).records
users(:users_a).assets.should include(assets(:asset_a_a))
users(:users_a).assets.should include(assets(:assets_a_b))
users(:users_a).assets.should include(assets(:assets_a_c))
You can finetune this like
users(:users_a).assets.find(assets(:asset_a_a).id).should include(assets(:asset_a_a))
users(:users_a).assets.find(assets(:asset_a_a).id).should_not include(assets(:asset_b_a))
If you absolutely want, you can use similar tests for the controller part.
Though there are quite some discussions if such basic functionality needs testing at all, since it's mostly core Rails functionality to handle the associations you defined in your model.
Personally I do this kind of testing for some reasons. In many cases such permission related associations soon become more complicated and need detailed tests anyway. or somebody may change the parameters of the association and break something.
II - About the current_user part within the controller.
This depends of course on how you handle the authentication to begin with. If you use a plugin like AuthLogic (or whatever) it may have some methods that allow simulating the login in rspec. For authlogic you can do something like this:
before(:each) do
activate_authlogic
UserSession.create(users(:user_a))
end
This will activate authlogic and 'login' user_a.
Then you run you controller
get :index
response.should be_success
response.should render_template :index
assigns(:assets).should # => more or less as above, check that there are the right aessets.
Related
So I'm running into a very weird issue when attempting to write some controller specs for a rails application I am working on. Basically at a high level it seems like the spec is never actually invoking the controller action. A couple of things that I've done and found..
After doing some looking around I attempted to see if it was possibly a routing issue and to test this I used controller.index and sure enough the action is invoked and the test passed. That being said this obviously leads to issues when I'm attempting to call actions that require params so I'd like to get #get|#post etc. working.
When I run expect(response).to be_successfuland write fail 'some message' in the controller action the test still passes, which further proves nothing is occurring surrounding the controller action.
I went about preventing devise from authenticating the user by removing before_filter :authenticate_user! from the application_controller and then for whatever reason the spec runs and hits the action correctly and the spec passes. Now, this is occurring even when using #get which works against the theory of this being a routing issue.
Because of the last point I think the problem lies somewhere in how the devise auth is being handled so to touch on that the only thing in this stack that isn't straight out of the box devise-wise is a controller helper for authentication that I use before hitting protected views which is pulled directly from the devise wiki on specs. It can be see here:
def sign_in(user = double('user'))
if user.nil?
allow(request.env['warden']).to receive(:authenticate!).and_throw(:warden, {:scope => :user})
allow(controller).to receive(:current_user).and_return(nil)
else
allow(request.env['warden']).to receive(:authenticate!).and_return(user)
allow(controller).to receive(:current_user).and_return(user)
end
end
Now even though I think the issue is in devise it's probably a good idea to give examples of everything else. So let me give you a lay of the land (of course some stuff is scrubbed out and simplified):
routes.rb:
namespace :api, path: '', constraints: { subdomain: 'api' }, defaults: { format: :json } do
namespace :v1 do
..
resources :accounts
end
end
alerts_controller.rb:
class AlertsController < ApplicationController
respond_to :html
def index
#alerts = Alert.all
end
end
alerts_controller_spec.rb:
require 'rails_helper'
RSpec.describe AlertsController, type: :controller do
let(:user){ create :user, :admin }
let(:alert){ create :alert }
before do
sign_in user
end
describe 'GET index' do
it 'responds successfully' do
get :index
expect(assigns(:alerts)).to eq [alert]
end
end
end
rake routes => GET /v1/alerts(.:format) api/v1/alerts#index {:subdomain=>"api"}
Any help would be highly appreciated as I'm seriously stumped.
Don't write your own helper for that. Devise comes with a helper just for this
Include it for your specs like this
#spec/rails_helper.rb
...
RSpec.configure do |config|
config.include Devise::TestHelpers, type: :controller
end
And then use it. In your case all you need to do is change this
before do
sign_in user
end
to this
before do
sign_in :user, user
end
I figured out the issue and honestly it was pretty stupid and my own fault :P. In my application controller there is a use_user_timezone around filter. The around filter was only added to callbacks if there was a current_user. Earlier on, for some reason, I prevented the code in the around filter from running unless Rails.env.test? == true. Obviously I didn't really have any reason for writing this code.. The problem was that I was not preventing the around_filter from running if it was the test environment, but that I was preventing the code in the filter method from running if it was the test environment. Because of this I was not yielding to any code to execute for the around filter, which was of course causing the issue with the controller action never executing.
The reason that devise was seeming to play an issue in the mix was because the around filter executing was dependent on a current_user being present. Due to this the controller flow was continuing on with no problem when no auth was occurring since there was no current_user.
The wrap this up.. As always it's best to be attentive when making changes like this to a codebase as little bugs like this can cause major headaches later on down the road :).
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
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 retroactively writing some tests, using RSpec, for a Rails project.
I'm using the CanCan gem to provide authorization. I decided to write a spec that will test the ability.rb model.
I then went on to test my remaining models.
I've moved on to controllers, and I've run into a huge snag: I'm testing my abilities all over again!
Basically, I have to stub out a series of models, and stub out their associations; otherwise the response just returns 403 Forbidden.
The reason for this, is that the controller is basically in charge of worrying about authorization.
I'm not quite sure where to go from here. I'm stubbing out up to 6 models, just to write a single test.
I know the abilities work, that's what ability_spec.rb is for.
So this question is really 2-fold:
Should I be testing the ability model separately?
Should the controller tests be concerned with proper permissions?
Edit
require 'spec_helper'
include Devise::TestHelpers # to give your spec access to helpers
describe TokensController do
before(:each) do
#mock_user = User.new(:username => "bob", :email => "user#user.com", :password => "longpassword")
#mock_user.role = "admin"
sign_in #mock_user
#Ability.stub!('can').and_return(true)
end
it "should let me see grids/:g_id/tokens index" do
test_grid = mock_model(Grid)
test_token = mock_model(Token)
Grid.stub!(:find).and_return(test_grid)
Token.stub!(:find).and_return(test_token)
get 'index'
a1 = Ability.new(#mock_user)
a1.can?(:index, Token).should be_true # This line works fine; as it should
puts response.status #This returns 403, which means CanCan::AccessDenied was raised
end
end
Thanks,
Robbie
Not sure if this is too late for you, but I just ran into the same issue, and solved it using the following code sample --
before do
#user = Factory.create(:user)
sign_in #user
#abilities = Ability.new(#user)
Ability.stub(:new).and_return(#abilities)
end
end
I've stubbed out Ability#new, giving me a reference to the instance of Ability that controls the current user. Then, I can stub out specific abilities like this:
#abilities.stub!(:can?).with(:destroy, regatta).and_return(true)
or give admin privileges:
#abilities.stub!(:can?).and_return(false)
I do test the cancan model separately, but testing what it will allow in what conditions.
I think if you are doing things like
authorize! :take_over, #the_world
Then I do think you should be testing that in the controller. I'm not sure you need to test ALL 6 versions of your models though.
You can stub out the Ability.can? class and have it respond true/false, and test how your controller handles when it can (and more importantly) when it cannot continue.
Similar to Sam's answer, but from the CanCan wiki page on testing:
Controller Testing
If you want to test authorization functionality at the controller level one option is to log-in the user who has the appropriate permissions.
user = User.create!(:admin => true) # I recommend a factory for this
# log in user however you like, alternatively stub `current_user` method
session[:user_id] = user.id
get :index
assert_template :index # render the template since he should have access
Alternatively, if you want to test the controller behavior independently from what is inside the Ability class, it is easy to stub out the ability with any behavior you want.
def setup
#ability = Object.new
#ability.extend(CanCan::Ability)
#controller.stubs(:current_ability).returns(#ability)
end
test "render index if have read ability on project" do
#ability.can :read, Project
get :index
assert_template :index
end
If you have very complex permissions it can lead to many branching possibilities. If these are all tested in the controller layer then it can lead to slow and bloated tests. Instead I recommend keeping controller authorization tests light and testing the authorization functionality more thoroughly in the Ability model through unit tests as shown at the top.
I think authorization needs to be done mainly for controllers to make sure your authorization is working correctly with your controllers. So to make it DRY you can implement your own matcher to be used like this
let!(:user) {create :user}
before { login_user_request user}
it "grants admin access to show action" do
expect{ get :show, {id: user.id} }.to be_authorized
end
it "denies user access to edit action" do
expect{ get :edit, {id: user.id} }.to be_un_authorized
end
and then implement these matchers with your own way to test how a request will be authorized or not
RSpec::Matchers.define :be_authorized do
match do |block|
block.call
expect(response).to be_success
end
def supports_block_expectations?
true
end
end
RSpec::Matchers.define :be_un_authorized do
match do |block|
expect{
block.call
}.to raise_error(Pundit::NotAuthorizedError)
end
def supports_block_expectations?
true
end
end
Why don't you include a
can :manage, :all do
user.is_ultrasuper == 1
end
in your Ability and then have a is_ultrasuper param in one of your fixture users:
one:
id: 1
username: my_username
is_ultrasuper: 1
Then log this user in at setup of your tests. This in tests you should be able to do anything at all.
I am trying to test my controller and maintain separation of concerns.
The first concern is "Who is able to execute which action?"
I am using authlogic for authentication and be9's acl9 for authorization. But this should not matter, all my authorization concerns are handled in a before_filter. I am testing such a before_filter by something similar to this:
describe SomeModelsController, "GET to index (authorization)" do
before(:each) do
#siteadmin = mock_model(User)
#siteadmin.stub!(:has_role?).with("siteadmin", nil).and_return(true)
end
it "should grant access to a siteadmin" do
controller.should_receive(:current_user).at_least(:once).and_return(#siteadmin)
get :index
response.should be_success
end
end
This spec is working just fine!
Now, the second concern is "Does the action do what it is supposed to do?"
This does not involve checking authorization. The best/cleanest solution would be skipping that before_filter all together and just do something like:
describe SomeModelsController, "GET to index (functional)" do
it "should find all Models" do
Model.should_receive(:find).with(:all)
end
end
Without having to worry about which user with which role has to logged in first. Right now I solved it like that:
describe SomeModelsController, "GET to index (functional)" do
before(:each) do
#siteadmin = mock_model(User)
#siteadmin.stub!(:has_role?).with("siteadmin", nil).and_return(true)
controller.stub!(:current_user).and_return(#siteadmin)
end
it "should find all Models" do
Model.should_receive(:find).with(:all)
end
end
If I now decided that my siteadmin does not have the right to access the index action anymore, it would not only break one spec - namely the spec that HAS to break in such a case - but also the totally unrelated second spec.
I know this is basically a minor issue, but it would be nice if somebody could come up with an (elegant) solution!
To skip the before filter:
controller.class.skip_before_filter :name_of_method_used_as_before_filter
The one caveat (mentioned the docs) is that this will only work for method-reference filters, not procs.
Alternatively, you could stub current_user.has_role?
describe SomeModelsController, "GET to index (functional)" do
before(:each) do
controller.current_user.stub!(:has_role?).and_return(true)
end
it "should find all Models" do
Model.should_receive(:find).with(:all)
end
end
Old question, but I had to solve this just recently. This will only work for Rails 3.1, for earlier versions you should replace _process_action_callbacks with filter_chain (untested)
You can simply delete a matching Proc from the filter chain like so (Test::Unit example):
class RandomControllerTest < ActionController::TestCase
def setup
#controller.class._process_action_callbacks.each do |f|
#controller.class._process_action_callbacks.delete(f) if f.raw_filter.to_s.match(/**<match for your proc>**/)
end
end
end
How about just not doing a GET request?
Try calling the controller method by itself.
controller.index