I'm trying to add a function to allow for quick testing of redirects for unauthenticated users. Here's what I have so far:
def unauthenticated_redirects_to redirect_path #yeild
context "when not signed in" do
it "redirects to #{redirect_path}" do
yield
expect(response).to redirect_to redirect_path
end
end
end
describe SomeController do
describe 'GET #show' do
unauthenticated_redirects_to('/some_path') { get :show }
context "when signed in" do
# One thing...
# Another thing...
end
end
describe 'GET #whatever' do
unauthenticated_redirects_to('/some_other_path') { get :whatever }
end
end
This doesn't work, however, since the scope and context of the primary describe block is not available to the block passed to unauthenticated_redirects_to. This reasonably leads to the error: undefined method `get' for RSpec::Core::ExampleGroup::Nested_1::Nested_2:Class.
Is there a way around this or is there a cleaner way to accomplish something similar which I should consider?
Here's an approach using shared examples which triggers the example based on shared metadata (:auth => true in this case) and which parses the example group description to pick up some key parameters.
require 'spec_helper'
class SomeController < ApplicationController
end
describe SomeController, type: :controller do
shared_examples_for :auth => true do
it "redirects when not signed in" do
metadata = example.metadata
description = metadata[:example_group][:description_args][0]
redirect_path = metadata[:failure_redirect]
http_verb = description.split[0].downcase.to_s
controller_method = description.match(/#(.*)$/)[1]
send(http_verb, controller_method)
expect(response).to redirect_to redirect_path
end
end
describe 'GET #show', :auth => true, :failure_redirect => '/some_path' do
context "when signed in" do
# One thing...
# Another thing...
end
end
describe 'GET #whatever', :auth => true, :failure_redirect => '/some_other_path' do
end
end
For completeness, here's another shared examples approach, this time using a block parameter with a before call which avoids the original scope problem:
require 'spec_helper'
class SomeController < ApplicationController
end
describe SomeController, type: :controller do
shared_examples_for 'auth ops' do
it "redirects when not signed in" do
expect(response).to redirect_to redirect_path
end
end
describe 'GET #show' do
it_behaves_like 'auth ops' do
let(:redirect_path) {'/some_path'}
before {get :show}
end
context "when signed in" do
# One thing...
# Another thing...
end
end
describe 'GET #new' do
it_behaves_like 'auth ops' do
let(:redirect_path) {'/some_other_path'}
before {get :whatever}
end
end
end
Have a look at rspec shared example.
Using shared_examples_for seemed like overkill given that I was only concerned with a single example. Furthermore, it_behaves_like("unauthenticated redirects to", '/some_other_path', Proc.new{ get :whatever}) seems unnecessarily verbose. The trick is to use #send() to maintain the proper scope.
def unauthenticated_redirects_to path, method_action
context "when not signed in" do
it "redirects to #{path} for #{method_action}" do
send(method_action.first[0], method_action.first[1])
expect(response).to redirect_to path
end
end
end
describe 'GET #new' do
unauthenticated_redirects_to '/path', :get => :new
end
Related
I have a custom AutorizationAdapter that I would like to test using RSpec:
class AdminAuthorization < ActiveAdmin::AuthorizationAdapter
def authorized?(_action, _subject = nil)
user.admin?
end
end
Initially I used a custom method but since I'm using Devise, using a custom AuthorizationAdapter seemed to be the way to go.
How would you go about testing it ? I tought one way to test it is to create a request spec for one of the controller and test for status code & redirection, something like that:
require 'rails_helper'
RSpec.describe 'AdminUsers', type: :request do
describe 'GET /admin_users' do
context 'admin' do
let(:admin_user) { create(:admin_user) }
before { sign_in super_user }
get admin_users_path
expect(response).to have_http_status(200)
end
context 'non admin' do
let(:user) { create(:user) }
before { sign_in user }
it 'redirects to the login page' do
get admin_users_path
expect(response).to have_http_status(302)
expect(response).to redirected_to '/admin/login'
end
end
context 'non logged in user' do
it 'redirects to the login page' do
get admin_users_path
expect(response).to have_http_status(302)
expect(response).to redirected_to '/admin/login'
end
end
end
end
I'm not sure this is the way to go.
These look reasonable to me. You can also look at the unit and feature specs that are in the ActiveAdmin test suite. However, AuthorizationAdapter itself is a PORO so you should be able to unit test in isolation: in the example given above that would be a fairly trivial test.
I'm new in testing and learning Rspec, and I can't git it working.
(I have read the book Effective testing with Rspec3, and many tutorials ...also pluralsight.com)
The situation is very simple. In a Companies controller I want to test de Create method, the company model belongs_to user, and is this the code:
I think the problem is when execute
in test: expect(Company).to receive(:new).with(company_params)
or in controller: #company.user=helpers.user
Controller:
class CompaniesController < SessionsController
def create
#company=Company.new(company_params)
#company.user=helpers.user
if #company.save()
redirect_to companies_path
else
render :edit
end
end
and Rspec:
RSpec.describe CompaniesController, type: :controller do
let(:user) { instance_double(User) }
before do
allow_any_instance_of(ApplicationHelper).to receive(:user){user}
allow(controller).to receive(:authorize){true}
end
describe 'Authenticated user with companies' do
let(:company_params) { {company:{name:"Albert",domain:"www.albert.com"}} }
let(:company) { instance_double(Company) }
before do
allow(Company).to receive(:new){company}
end
describe 'POST #create' do
context "with valid data" do
before { allow(company).to receive(:save){true} }
it "redirects to companies_path" do
expect(Company).to receive(:new).with(company_params)
expect(company).to receive(:user=).with(user)
post :create, params:{company: company_params}
expect(response).to redirect_to(companies_path)
end
My intention is very simple: Use instance_double to mock (or stub) #company, and Company.new, using instance double...to test the create action, and simulate the "save()" returning true...etc
I do not know if I explain myself very well, but given the create action of controlloer , how to test using mocks ans stubs, instance_double?
Thanks
First of all let me explain what we need to test here
def create
#company=Company.new(company_params)
#company.user=helpers.user
if #company.save()
redirect_to companies_path
else
render :edit
end
end
We are testing create action of a controller. First let us see what this action does? It's just takes comapany_params as input and create a company record in database.
Testing also goes like the same, we need to just pass the input that action required, and need to check whether it's creating record in database or not.
RSpec.describe CompaniesController, type: :controller do
let(:user) { instance_double(User) }
before do
# all your authentication stubing goes here
allow_any_instance_of(ApplicationHelper).to receive(:user){user}
allow(controller).to receive(:authorize){true}
end
describe 'POST#create' do
context 'with valid attributes' do
before do
post :create, { company:{ name:"Albert", domain:"www.albert.com"} }
end
it 'responds with success' do
expect(response.status).to eq(302)
end
it 'creates company' do
company = Company.find_by(name: "Albert")
expect(assigns(:company)).to eq(company)
expect(response).to redirect_to(companies_path())
end
end
context 'with invalid attributes' do
before do
post :create, { company:{ name:"", domain:""} }
end
it 'renders new template' do
expect(response).to render_template(:edit)
end
end
end
end
No need to sub anything here. As per my knowledge, Only when we use any lib classes / background jobs / third party libraries code inside action then we need to stub those code. Because for all those, we will write specs separately. So no need to test again here that's why we'll do stubing.
Thanks to Narsimha Reddy, I have better ideas about how to test.
Eventhough, if I want to stub
#company=Company.new(company_params)
#company.user=helpers.user
if #company.save()
For testing only de create's response , the solution was in a good use of parameters, and allowing allow(company).to receive(:user=) for the belongs_to association
let(:company_params) {{company:{name:"Albert",domain:"www.albert.com"}}}
let(:ac_company_params) {ActionController::Parameters.new(company_params).require(:company).permit!}
let(:company) { instance_double(Company) }
before do
allow(Company).to receive(:new){company}
allow(company).to receive(:user=)
allow(company).to receive(:save){true}
end
it "redirects to companies_path" do
expect(Company).to receive(:new).with(ac_company_params)
expect(company).to receive(:user=).with(user)
post :create, params: company_params
expect(response).to redirect_to(companies_path)
end
I'm new to RSpec and I'm just wondering how to reuse a context across several actions in a controller. Specifically, I have code like this:
describe "GET index" do
context "when authorized" do
...
end
context "when unauthorized" do
it "denys access"
end
end
describe "GET show" do
context "when authorized" do
...
end
context "when unauthorized" do
it "denys access"
end
end
...
And I'd like to DRY it up a bit. The unauthorized context is the same on every action, how can I reuse it?
Shared examples are your friend:
Create a new file, something like spec/shared/unauthorized.rb and include it in your spec_helper then format it like this:
shared_examples_for "unauthorized" do
context "when unauthorized" do
it "denys access"
end
end
Then in your specs:
include_examples "unauthorized"
Do that in each describe block and you should be golden.
if you use popular gem Devise, you can reuse devise mapping like this:
require "spec_helper"
describe Worksheet::CompanyController do
login_user_admin #<= this macros on /spec/support/controller_macros.rb
describe '#create' do
it 'admin login and create worksheet' do
post :create, worksheet_company: attributes_for(:"Worksheet::Company")
expect(response.status).to eq(302)
expect(response).to redirect_to(admin_root_path)
end
end
create and login admin_user
spec/support/controller_macros.rb
module ControllerMacros
def login_user_admin
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user_admin]
user_admin = FactoryGirl.create(:user_admin)
user_admin.confirm!
sign_in user_admin
end
end
end
on spec/spec_helper.rb:
RSpec.configure do |config|
...
config.include Devise::TestHelpers, type: :controller
config.extend ControllerMacros, type: :controller
...
end
I have the following tests that I want tested from various actions in the same controller. How can I DRY this up? In the comments below you'll see that the test should call a different method and action depending on which action I'm testing.
shared_examples_for "preparing for edit partial" do
it "creates a new staff vacation" do
StaffVacation.should_receive(:new)
get :new
end
it "assigns #first_day_of_week" do
get :new
assigns(:first_day_of_week).should == 1
end
end
describe "GET new" do
# i want to use 'it_behaves_like "preparing for edit partial"'
# and it should use 'get :new'
end
describe "GET edit" do
# i want to use 'it_behaves_like "preparing for edit partial"'
# but it should use 'get :edit' instead
end
describe "POST create" do
# on unsuccessful save, i want to use 'it_behaves_like "preparing for edit partial"'
# but it should use 'post :create' instead
end
You could do something like this:
shared_examples_for "preparing for edit partial" do
let(:action){ get :new }
it "creates a new staff vacation" do
StaffVacation.should_receive(:new)
action
end
it "assigns #first_day_of_week" do
action
assigns(:first_day_of_week).should == 1
end
end
context 'GET new' do
it_should_behave_like 'preparing for edit partial' do
let(:action){ get :new }
end
end
context 'GET edit' do
it_should_behave_like 'preparing for edit partial' do
let(:action){ get :edit }
end
end
context 'POST create' do
it_should_behave_like 'preparing for edit partial' do
let(:action){ post :create }
end
end
Or, you could use some kind of loop for the examples:
['get :new', 'get :edit', 'post :create'].each do |action|
context action do
it "creates a new staff vacation" do
StaffVacation.should_receive(:new)
eval(action)
end
it "assigns #first_day_of_week" do
eval(action)
assigns(:first_day_of_week).should == 1
end
end
end
One option might be to provide a module mix-in with a method that has your spec inside it.
include Auth # This is your module with your generalized spec inside a method
it "redirects without authentication" do
unauthorized_redirect("get", "new")
end
Then, in our method, we could do a loop through different types of authorization:
module Auth
def unauthorized_redirect(request, action)
[nil, :viewer, :editor].each do |a|
with_user(a) do
eval "#{request} :#{action}"
response.should redirect_to login_path
# whatever other expectations
end
end
end
end
I'm trying to keep my specs DRY by creating a shared example group that performs the boilerplate checks for all admin controllers (all controllers under the Admin namespace of my project). I'm struggling to figure out how to do it, since the shared example needs providing with the information about what actions and parameters to use. It should ideally present meaningful errors if a test fails (i.e. include the details of the action it was testing).
require 'spec_helper'
shared_examples "an admin controller" do
before(:each) do
#non_admin = User.make
#admin = User.make(:admin)
end
context "as an admin user" do
#actions.each do |action, params|
specify "I should be able to access ##{action.last} via #{action.first}" do
self.active_user = #admin
send(action.first, action.last, params)
response.status.should be_ok
end
end
end
context "as a regular user" do
#actions.each do |action, params|
specify "I should be denied access to ##{action.last}" do
self.active_user = #non_admin
send(action.first, action.last, params)
response.status.should be 403
end
end
end
end
describe Admin::UserNotesController do
#user = User.make
#actions = { [:get, :index] => { :user_id => #user.id },
[:get, :new] => { :user_id => #user.id },
[:post, :create] => { :user_id => #user.id } }
it_behaves_like "an admin controller"
end
This errors for the obvious reason that #actions is not visible to the shared example group. If I use let, this is only available in the context of an example, not in the context of the describe block. Any ideas?
Here's a much cleaner way that should work:
require 'spec_helper'
shared_examples "an admin controller" do |actions|
context "as an admin user" do
actions.each_pair do |action, verb|
specify "I should be able to access ##{action} via #{verb}" do
send(verb, action, :user_id => User.make(:admin).id)
response.status.should be_ok
end
end
end
context "as a regular user" do
actions.each_pair do |action, verb|
specify "I should be denied access to ##{action}" do
send(verb, action, :user_id => User.make.id)
response.status.should be 403
end
end
end
end
describe Admin::UserNotesController do
it_behaves_like "an admin controller", {
:index => :get,
:new => :get,
:create => :post
}
end
See http://relishapp.com/rspec/rspec-core/v/2-6/dir/example-groups/shared-examples for more information