Rspec 2.7 access controller session in spec before making request - ruby-on-rails

I'm testing my controllers using Rspec and I can't seem to set the session variable of the current controller under test before making the request to the path.
For example this works:
describe "GET /controller/path" do
it "if not matching CRSF should display message" do
get controller_path
request.session[:state] = "12334"
end
end
This doesn't work (i get an error saying session is not a method of Nil class):
describe "GET /controller/path" do
it "if not matching CRSF should display message" do
request.session[:state] = "12334"
get controller_path
end
end
Any ideas?

With new version of RSpec this is done pretty nice, look:
describe SessionController do
# routes are mapped as:
# match 'login' => 'session#create'
# get 'logout' => 'session#destroy'
describe "#create" do
context "with valid credentials" do
let :credentials do
{ :email => 'example#gmail.com', :password => 'secret' }
end
let :user do
FactoryGirl.create(:user, credentials)
end
before :each do
post '/login', credentials
end
it "creates a user session" do
session[:user_id].should == user.id
end
end
# ...
end
describe "#destroy" do
context "when user logged in" do
before :each do
get "/logout", {}, { :user_id => 123 } # the first hash is params, second is session
end
it "destroys user session" do
session[:user_id].should be_nil
end
# ...
end
end
end
You can also use simply request.session[:user_id] = 123 inside before(:each) block, but above looks pretty nicer.

Try this:
describe "GET /controller/path" do
it "if not matching CRSF should display message" do
session[:state] = "12334"
get controller_path
end
end

Related

Abstracting out common setup steps in RSpec test

I'm trying to get my head around the best way to create a user token in my RSpec tests, and write them as eloquently as I can.
Below is one example for my Project class. From the spec below, you'll see I'm using DoorKeeper to keep the API endpoints secure on all actions other than show.
The problem I'm running into is how best to create that #access_token.
This works, passing all the examples, however I'm worried I'm not adhering to DRY principles. If lots of actions/contexts require an #access_token is there a way I could abstract this out somewhere to a helper of sorts?
Thanks in advance
## projects_spec.rb
require 'spec_helper'
describe "Projects API" do
describe "#index" do
FactoryGirl.create(:project)
context 'with a valid token' do
before(:each) do
user = FactoryGirl.create(:user)
authentication = FactoryGirl.create(:authentication, user: user)
application = Doorkeeper::Application.create!(:name => "MyApp", :redirect_uri => "http://app.com")
#access_token = Doorkeeper::AccessToken.create!(:application_id => application.id, :resource_owner_id => authentication.identity.id)
end
it 'returns a list of projects' do
get '/api/v1/projects', access_token: #access_token.token
expect(response.status).to eq(200)
# check the JSON is as we expect
end
end
context 'without a token' do
it 'responds with 401' do
get '/api/v1/projects'
expect(response.status).to eq(401)
end
end
end
describe "#create" do
context 'with a valid token' do
before(:each) do
user = FactoryGirl.create(:user)
authentication = FactoryGirl.create(:authentication, user: user)
application = Doorkeeper::Application.create!(:name => "MyApp", :redirect_uri => "http://app.com")
#access_token = Doorkeeper::AccessToken.create!(:application_id => application.id, :resource_owner_id => authentication.identity.id)
end
context 'with required params' do
project_params = {
name: "Win the lottery",
strapline: "The best feeling in the world"
}
it "creates a project and responds with 201" do
post "/api/v1/projects", :project => project_params, access_token: #access_token.token
expect(response.status).to eq(201)
# check the JSON is as we expect
end
end
context 'without required params' do
project_params = {
strapline: "Stepney City Farm's pallets, woodchips and compost",
}
it "responds with 422 and no record created" do
post "/api/v1/projects", :project => project_params, access_token: #access_token.token
expect(response.status).to eq(422)
json = JSON.parse(response.body)
expect(json['project']['errors'].length).to eq(1)
end
end
end
context 'without a token' do
it 'responds with 401' do
get '/api/v1/projects'
expect(response.status).to eq(401)
end
end
end
describe "#show" do
it 'returns a projects' do
project = FactoryGirl.create(:project, name: "A new project")
get "/api/v1/projects/#{project.id}"
expect(response.status).to eq(200)
json = JSON.parse(response.body)
expect(json['project']['name']).to eq(project.name)
expect(GroupRun.last.name).to eq(project.name)
# check the JSON is as we expect
end
end
end
I have a few techniques that I use to deal with this stuff.
The first is to use plain ruby methods over let. This is just my preference, I think it adds clarity to tests. Check this for more about that: http://robots.thoughtbot.com/lets-not
And then, I have a helper method for auth stuff. I'm using Devise for authentication, so my implementation will be different than yours, but this sets the HTTP_AUTHORIZATION header for every request made to my app after calling the helper method in the tests.
module Requests
module AuthenticationHelpers
def basic_http_auth(user)
credentials = ActionController::HttpAuthentication::Basic.encode_credentials(user.email,user.password)
Rack::MockRequest::DEFAULT_ENV['HTTP_AUTHORIZATION'] = credentials
end
end
end
And then in the actual test, I'll do something like this:
describe "GET /api/v1/messages" do
it 'returns an array of messages' do
get '/api/v1/messages'
basic_http_auth(FactoryGirl.create(:user))
expect(response).to eq(200)
end
end
So, if you're going to be using this across a lot of the API tests, move it into a support helper. And, if you're calling something multiple times in the same file, write a method (or put it in a let call) to DRY your your tests.
Building on Matthew's answer, with Doorkeeper, I ended up using the following:
module TokenMacros
def generate_access_token_for(user = nil)
user ||= FactoryGirl.create(:user)
authentication = FactoryGirl.create(:authentication, user: user)
application = Doorkeeper::Application.create!(:name => "MyApp", :redirect_uri => "http://app.com")
access_token = Doorkeeper::AccessToken.create!(:application_id => application.id, :resource_owner_id => authentication.identity.id)
access_token.token
end
end
which then allows me to call...
let(:user) { FactoryGirl.create(:user) }
let(:token) { generate_access_token_for(user) }
splendid
The easiest thing to do to avoid the duplication would be to define the token in the highest level describe so that it's available for all the examples in your spec.
In addition, to avoid any performance issue for the examples that don't depend on it, you can use let rather than before to define the token since let is lazily evaluated.
What you'd have then, is the following:
let(:access_token) do
user = FactoryGirl.create(:user)
authentication = FactoryGirl.create(:authentication, user: user)
application = Doorkeeper::Application.create!(:name => "MyApp", :redirect_uri => "http://app.com")
Doorkeeper::AccessToken.create!(:application_id => application.id, :resource_owner_id => authentication.identity.id)
end
You'd need to also change your references to this token from #access_token to access_token, since you're defining a method and not an instance variable.

Generating RSpec Examples via Functions

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

How to test after_sign_in_path_for(resource)?

I have devise authentication and registration set up on my Rails app. I'm using after_sign_in_path_for() to customise the redirect when the user signs in based on various scenarios.
What I'm asking is how to test this method? It seems hard to isolate since it is called automatically by Devise when the user signes in. I want to do something like this:
describe ApplicationController do
describe "after_sign_in_path_for" do
before :each do
#user = Factory :user
#listing = Factory :listing
sign_in #user
end
describe "with listing_id on the session" do
before :each do
session[:listing_id] = #listing.id
end
describe "and a user in one team" do
it "should save the listing from the session" do
expect {
ApplicationController.new.after_sign_in_path_for(#user)
}.to change(ListingStore, :count).by(1)
end
it "should return the path to the users team page" do
ApplicationController.new.after_sign_in_path_for(#user).should eq team_path(#user.team)
end
end
end
end
end
but that's obviously not the way to do it because I just get an error:
Failure/Error: ApplicationController.new.after_sign_in_path_for(#user)
RuntimeError:
ActionController::Metal#session delegated to #_request.session, but #_request is nil: #<ApplicationController:0x00000104581c68 #_routes=nil, #_action_has_layout=true, #_view_context_class=nil, #_headers={"Content-Type"=>"text/html"}, #_status=200, #_request=nil, #_response=nil>
So, how can I test this method?
Oddly, I was wondering this very thing today. Here's what I came up with. I created an anonymous subclass of ApplicationController. In this anonymous subclass, I exposed the protected methods that I wanted to test as public methods. Then I tested them directly.
describe ApplicationController do
controller do
def after_sign_in_path_for(resource)
super resource
end
end
before (:each) do
#user = FactoryGirl.create(:user)
end
describe "After sigin-in" do
it "redirects to the /jobs page" do
controller.after_sign_in_path_for(#user).should == jobs_path
end
end
end
On a similar note - if you want to test the redirect after sign-up, you have two options.
First, you can follow a pattern similar to above and very directly test the method in RegistrationsController:
require 'spec_helper'
describe RegistrationsController do
controller(RegistrationsController) do
def after_sign_up_path_for(resource)
super resource
end
end
describe "After sign-up" do
it "redirects to the /organizations/new page" do
#user = FactoryGirl.build(:user)
controller.after_sign_up_path_for(#user).should == new_organization_path
end
end
end
Or, you can take a more integration-testing sort of approach and do the following:
require 'spec_helper'
describe RegistrationsController do
describe "After successfully completing the sign-up form" do
before do
#request.env["devise.mapping"] = Devise.mappings[:user]
end
it "redirects to the new organization page" do
post :create, :user => {"name" => "Test User", "email" => "test#example.com", "password" => "please"}
response.should redirect_to(new_organization_path)
end
end
end
For the newcomers, I would recommend doing this way:
RSpec.describe ApplicationController, type: :controller do
let(:user) { create :user }
describe "After sing-in" do
it "redirects to the /yourpath/ home page" do
expect(subject.after_sign_in_path_for(user)).to eq(yourpath_root_path)
end
end
end
I found this answer through Google recently and thought I would add my solution. I didn't like the accepted answer because it was testing the return value of a method on the application controller vs testing the desired behavior of the app.
I ended up just testing the call to create a new sessions as a request spec.
RSpec.describe "Sessions", type: :request do
it "redirects to the internal home page" do
user = FactoryBot.create(:user, password: 'password 123', password_confirmation: 'password 123')
post user_session_path, params: {user: {email: user.email, password: 'password 123'}}
expect(response).to redirect_to(internal_home_index_path)
end
end
(Rails 5, Devise 4, RSpec 3)
context "without previous page" do
before do
Factory.create(:user, email: "junior#example.com", password: "123456", password_confirmation: "123456")
request.env["devise.mapping"] = Devise.mappings[:user]
post :create, user: { email: "junior#example.com", password: "123456" }
end
end
it { response.should redirect_to(root_path) }
context "with previous page" do
before do
Factory.create(:user, email: "junior#example.com", password: "123456", password_confirmation: "123456")
request.env["devise.mapping"] = Devise.mappings[:user]
request.env['HTTP_REFERER'] = 'http://test.com/restaurants'
post :create, user: { email: "junior#example.com", password: "123456" }
end
it { response.should redirect_to("http://test.com/restaurants") }
end

Dynamically generating shared examples in RSpec 2?

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

Rails/Rspec Make tests pass with http basic authentication

Here my http basic authentication in the application controller file (application_controller.rb)
before_filter :authenticate
protected
def authenticate
authenticate_or_request_with_http_basic do |username, password|
username == "username" && password == "password"
end
end
and the default test for the index action of my home controller (spec/controllers/home_controller_spec.rb)
require 'spec_helper'
describe HomeController do
describe "GET 'index'" do
it "should be successful" do
get 'index'
response.should be_success
end
end
Test doesn't run because of the authentication method. I could comment "before_filter :authenticate" to run them but I would like to know if there is way to make them worked with the method.
Thank you!
Update (2013): Matt Connolly has provided a GIST which also works for request and controller specs: http://gist.github.com/4158961
Another way of doing this if you have many tests to run and don't want to include it everytime (DRYer code):
Create a /spec/support/auth_helper.rb file:
module AuthHelper
def http_login
user = 'username'
pw = 'password'
request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(user,pw)
end
end
In your test spec file:
describe HomeController do
render_views
# login to http basic auth
include AuthHelper
before(:each) do
http_login
end
describe "GET 'index'" do
it "should be successful" do
get 'index'
response.should be_success
end
end
end
Credit here - Archived site
Sorry I didn't seek enough, the solution seems to be the following:
describe "GET 'index'" do
it "should be successful" do
#request.env["HTTP_AUTHORIZATION"] = "Basic " + Base64::encode64("username:password")
get 'index'
response.should be_success
end
end
Some answers suggest to set request.env which is unsafe, because request can be nil and you will end up with private method env' called for nil:NilClass, especially when run single tests with rspec -e
Correct approach will be:
def http_login
user = 'user'
password = 'passw'
{
HTTP_AUTHORIZATION: ActionController::HttpAuthentication::Basic.encode_credentials(user,password)
}
end
get 'index', nil, http_login
post 'index', {data: 'post-data'}, http_login
For me, with Rails 6, I need keyword arguments for rspec get method like .. get route, params: params, headers: headers
Auth Helper method
module AuthHelper
def headers(options = {})
user = ENV['BASIC_AUTH_USER']
pw = ENV['BASIC_AUTH_PASSWORD']
{ HTTP_AUTHORIZATION: ActionController::HttpAuthentication::Basic.encode_credentials(user,pw) }
end
def auth_get(route, params = {})
get route, params: params, headers: headers
end
end
and the rspec request test.
describe HomeController, type: :request do
include AuthHelper
describe "GET 'index'" do
it "should be successful" do
auth_get 'index'
expect(response).to be_successful
end
end
end
When using Rspec to test Grape APIs, the following syntax works
post :create, {:entry => valid_attributes}, valid_session
where valid_session is
{'HTTP_AUTHORIZATION' => credentials}
and
credentials = ActionController::HttpAuthentication::Token.encode_credentials("test_access1")
These are great solutions for controller and request specs.
For feature tests using Capybara, here is a solution to make HTTP Basic authentication work:
spec/support/when_authenticated.rb
RSpec.shared_context 'When authenticated' do
background do
authenticate
end
def authenticate
if page.driver.browser.respond_to?(:authorize)
# When headless
page.driver.browser.authorize(username, password)
else
# When javascript test
visit "http://#{username}:#{password}##{host}:#{port}/"
end
end
def username
# Your value here. Replace with string or config location
Rails.application.secrets.http_auth_username
end
def password
# Your value here. Replace with string or config location
Rails.application.secrets.http_auth_password
end
def host
Capybara.current_session.server.host
end
def port
Capybara.current_session.server.port
end
end
Then, in your spec:
feature 'User does something' do
include_context 'When authenticated'
# test examples
end
My solution:
stub_request(method, url).with(
headers: { 'Authorization' => /Basic */ }
).to_return(
status: status, body: 'stubbed response', headers: {}
)
Use gem webmock
you can tighten verification by change:
/Basic */ -> "Basic #{Base64.strict_encode64([user,pass].join(':')).chomp}"
URL - can be a regular expression

Resources