Rails noob here. I'm having trouble understanding how/what to test for regarding authentication with Omniauth-Facebook. Pretty much have a similar setup to the relevant railscast. I have my test environment set up similarly to what's described in previous questions and on the Gem wiki.
Couple of questions. When you create a User factory how do you get resulting object to mock an authentication?
Also what goes on when the following code is run?
before do
request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:facebook]
visit '/auth/facebook'
end
Is an object saved in a database?
I've added some sample specs below and the Factory spec. Again the spec_helper setup file has the test mode config set to true.
The add_mock setup:
OmniAuth.config.add_mock(:facebook,
{ :provider => 'facebook',
:uid => '1234567',
:info => { :name => 'Jonathan', :image => 'http://graph.facebook.com/1234567/picture?type=square'},
:credentials => {
:expires_at => 1351270850,
:token=> 'AAADzk0b791YBAHCNhBI3n6ScmWvuXTY4yIUqXr9WiZCg1R808RYzaHxsHnrbn62IwrIgZCfSBZAVIP6ptF41nm8YtRtZCeBbxbbz1mF8RQZDZD'
} })
User_pages_spec:
describe "user pages" do
let(:user) { Factory.create(:user) }
describe "sign_in" do
before { visit '/' }
it "should add a user to the User model" do
expect { click_link "sign_in" }.to change(User, :count).by(1)
end
it "should route to the appropriate page if email is nil" do
click_link "sign_in"
page.should have_selector('h5', text: 'Edit Email')
end
it "should redirect to the show page upon" do
user.email = 'example#stanford.edu'
user.save!
click_link 'sign_in'
page.should have_selector('div', text: user.name)
end
end
end
Factory
FactoryGirl.define do
factory :user do
provider "facebook"
uid '1234567'
name 'Jonathan'
end
end
Related
I've got custom member_action in my Active Admin panel which is responsible for resending devise reset password instructions.
admin/users.rb
ActiveAdmin.register User do
member_action :reset_password do
user = User.find(params[:id])
user.send_reset_password_instructions
redirect_to(admin_user_path(user),
notice: "Password reset email sent to #{user.email}")
end
end
How to write RSpec tests for such an action? The only thing I found is this one and I think it's not quite related to my problem.
I was trying to sth like below:
require 'rails_helper'
describe Admin::UsersController, type: :controller do
include Devise::TestHelpers
let!(:admin) { create(:admin_user) }
before(:each) do
sign_in admin
end
describe 'GET user' do
let(:user) { create(:user, :random_email) }
before(:each) do
User.should_receive(:find).at_least(:once).and_return(user)
get :show
end
it 'sends email' do
get :reset_password
expect(user).should_receive(:send_reset_password_instructions)
end
end
end
But I'm getting an error:
ActionController::UrlGenerationError:
No route matches {:action=>"reset_password", :controller=>"admin/users"}
Personally I prefer to use a feature test, since when using active admin, UI stuff handle by the framework:
RSpec.feature 'Reset Password', type: :feature do
let(:user) { create :user }
before do
login_as(user, scope: :user)
end
scenario 'can delete future episode' do
visit some_path
click_link 'Reset Password'
expect(page.current_path).to eq(admin_user_path(user))
expect(page).to have_content("Password reset email sent to #{user.email}")
end
end
Ok, it turns out small adjustments (pass the user.id in params) make the trick.
describe Admin::UsersController, type: :controller do
include Devise::Test::ControllerHelpers
before { sign_in admin }
let!(:admin) { create(:admin_user) }
describe 'GET user' do
let(:user) { create(:user, :random_email) }
before do
allow(User).to receive(:find).at_least(:once) { user }
get :show, params: { id: user.id }
end
it 'sends email' do
get :reset_password, params: { id: user.id }
expect(flash[:notice]).to match("Password reset email sent to #{user.email}")
end
end
end
I'm using Auth0 for authentication in my rails app. I need to write some feature tests for login and signup. I can't seem to find something concrete on how to do this with rspec and capybara.
Tried doing something along the lines explained in this gist but it still doesn't work. If someone has had experience with rspec feature tests with Auth0 I'd appreciate if you would guide me in the right direction.
Thanks!
My configuration
# in spec/support/omniauth_macros.rb
module OmniauthMacros
def mock_auth_hash
# The mock_auth configuration allows you to set per-provider (or default)
# authentication hashes to return during integration testing.
OmniAuth.config.mock_auth[:auth0] = {
'provider' => 'auth0',
'uid' => '123545',
'user_info' => {
'name' => 'mockuser',
'image' => 'mock_user_thumbnail_url'
},
'credentials' => {
'token' => 'mock_token',
'secret' => 'mock_secret'
}
}
end
end
# in spec/requests/spec_helper.rb
RSpec.configure do |config|
# ...
# include our macro
config.include(OmniauthMacros)
end
OmniAuth.config.test_mode = true
Then in my spec I have
scenario 'Should successfully login user' do
visit login_path
mock_auth_hash
click_link "Sign in"
expect(page).to have_content('Signed in successfully')
expect(page).to have_link("Logout", href: logout_path)
end
This is how I solved it
# in spec/support/omniauth_macros.rb
module OmniauthMacros
def mock_valid_auth_hash(user)
# The mock_auth configuration allows you to set per-provider (or default)
# authentication hashes to return during integration testing.
opts = {
"provider": user.provider,
"uid": user.uid,
"info": {
"email": user.email,
"first_name": user.first_name,
"last_name": user.last_name,
},
"credentials": {
"token": "XKLjnkKJj7hkHKJkk",
"expires": true,
"id_token": "eyJ0eXAiOiJK1VveHkwaTFBNXdTek41dXAiL.Wz8bwniRJLQ4Fqx_omnGDCX1vrhHjzw",
"token_type": "Bearer"
}
}
OmniAuth.config.mock_auth[:auth0] = OmniAuth::AuthHash.new(opts)
end
def mock_invalid_auth_hash
OmniAuth.config.mock_auth[:auth0] = :invalid_credentials
end
end
# in spec/support/features/session_helpers.rb
module Features
module SessionHelpers
def log_in(user, invalid=false, strategy = :auth0)
invalid ? mock_invalid_auth_hash : mock_valid_auth_hash(user)
Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[strategy.to_sym]
visit "/auth/#{strategy.to_s}/callback?code=vihipkGaumc5IVgs"
end
end
end
# in spec/support/rails_helper.rb
RSpec.configure do |config|
# ...
# include our macro
config.include(OmniauthMacros)
# include Session helpers
config.include Features::SessionHelpers, type: :feature
end
And finally my feature test to simulate login looks like
# user signin feature test
require 'rails_helper'
feature 'Login with Omniauth' do
given(:user) { create(:user) }
context "With valid credentials" do
scenario 'Should successfully login user' do
log_in(user)
expect(page).to have_content("successfully signed in")
expect(page).to have_link("Logout", href: logout_path)
end
end
context "With invalid credentials" do
scenario "Should not log in user" do
log_in(user, true)
expect(page).to have_content('invalid_credentials')
end
end
end
Hope this helps!
I've been learning Rails 3 with Devise and, so far, seem to have it working quite well. I've got custom session & registration controllers, recaptcha is working and a signed-in user can upload an avatar via carrierwave, which is saved on S3. Pretty happy with my progress.
Now I'm writing Rspec tests. Not going so well! I have a reasonable User model test, but that's because I found it online (https://github.com/RailsApps/rails3-devise-rspec-cucumber/) and was able to add to it by following Michael Hartl's excellent "Ruby on Rails 3 Tutorial".
My real problem is controller test and integration tests, especially controller tests. Initially I thought I'd be able to convert the tests in Michael's book, and I have to a small degree, but it's slow progress and I seem to be constantly hitting my head against a brick wall - partly, I think, because I don't know Rspec and capybara so well (have made some very dumb mistakes) but also because I don't really understand Devise well enough and am wondering if Devise plays as nicely as it might with Rspec; I read somewhere that, because Devise is Rack based, it might not always work as one might expect with Rspec. Don't know if that's true or not?
I know some people will wonder why this might be necessary since Devise is a gem and therefore already tested but I've had a couple of instances where changes elsewhere have broken login or registration without me immediately realizing. I think a good set of controller & integration tests would have solved this.
If I was able to do this myself I would and I'd publish it for others but, so far, writing these tests has been extremely painful and I really need to move on to other things.
I'm sure I wouldn't be the only one who could use this. Anyone know of a such a suite of tests?
In response to Jesse's kind offer of help...
Here is my registrations_controller_spec. The comments in "should render the 'edit' page" show the sort of things I am struggling with. Also, "should create a user" has some things I've tried to test but not been able to:
require File.dirname(__FILE__) + '/../spec_helper'
describe Users::RegistrationsController do
include Devise::TestHelpers
fixtures :all
render_views
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
end
describe "POST 'create'" do
describe "failure" do
before(:each) do
#attr = { :email => "", :password => "",
:password_confirmation => "", :display_name => "" }
end
it "should not create a user" do
lambda do
post :create, :user_registration => #attr
end.should_not change(User, :count)
end
it "should render the 'new' page" do
post :create, :user_registration => #attr
response.should render_template('new')
end
end
describe "success" do
before(:each) do
#attr = { :email => "user#example.com",
:password => "foobar01", :password_confirmation => "foobar01", :display_name => "New User" }
end
it "should create a user" do
lambda do
post :create, :user => #attr
response.should redirect_to(root_path)
#response.body.should have_selector('h1', :text => "Sample App")
#response.should have_css('h1', :text => "Sample App")
#flash[:success].should == "A message with a confirmation link has been sent to your email address. Please open the link to activate your account."
#response.should have_content "A message with a confirmation link has been sent to your email address. Please open the link to activate your account."
end.should change(User, :count).by(1)
end
end
end
describe "PUT 'update'" do
before(:each) do
#user = FactoryGirl.create(:user)
#user.confirm! # or set a confirmed_at inside the factory. Only necessary if you are using the confirmable module
sign_in #user
end
describe "Failure" do
before(:each) do
# The following information is valid except for display_name which is too long (max 20 characters)
#attr = { :email => #user.email, :display_name => "Test", :current_password => #user.password }
end
it "should render the 'edit' page" do
put :update, :id => subject.current_user, :user => #attr
# HAVE PUT THE DEBUGS THAT I'D LIKE TO GET WORKING FIRST
# Would like to be able to debug and check I'm getting the error(s) I'm expecting
puts subject.current_user.errors.messages # doesn't show me the errors
# Would like to be able to debug what html is being returned:
puts page.html # only return the first line of html
# Would like to be able to determine that this test is failing for the right reasons
response.should have_content "Display name is too long (maximum is 20 characters)" # doesn't work
response.should render_template('edit')
end
end
describe "Success" do
it "should change the user's display name" do
#attr = { :email => #user.email, :display_name => "Test", :current_password => #user.password }
put :update, :id => subject.current_user, :user => #attr
subject.current_user.reload
response.should redirect_to(root_path)
subject.current_user.display_name == #attr[:display_name]
end
end
end
describe "authentication of edit/update pages" do
describe "for non-signed-in users" do
before(:each) do
#user = FactoryGirl.create(:user)
end
describe "for non-signed-in users" do
it "should deny access to 'edit'" do
get :edit, :id => #user
response.should redirect_to(new_user_session_path)
end
it "should deny access to 'update'" do
put :update, :id => #user, :user => {}
response.should redirect_to(new_user_session_path)
end
end
end
end
end
Acording to Devise Wiki, controller tests have to be some kind of hibrid (unit + integration) tests. You have to create an instance of User (or you auth entity) in the database. Mocking Devise stuff is really hard, trust me.
Try this: How-To:-Controllers-and-Views-tests-with-Rails-3-and-rspec
With Cucumber, you can create a step that already do the login process.
Hope it helps.
OK, so a couple of things to make this work:
Here's how you should test that the body have specific text (you were missing the .body
response.body.should include "Display name is too long (maximum is 30 characters)"
You could also test that the #user has an error:
assigns[:user].errors[:display_name].should include "is too long (maximum is 30 characters)"
But, you do need to actually make the name be over 20/30 characters long, so I changed the attributes being posted to:
#attr = { :email => #user.email, :display_name => ("t" * 35), :current_password => #user.password }
https://gist.github.com/64151078628663aa7577
I have the following route:
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks",
:registrations => 'users/registrations',
:sessions => "users/sessions" }
and the following controller test (registrations_controller_spec.rb):
require File.dirname(__FILE__) + '/../spec_helper'
describe Users::RegistrationsController do
include Devise::TestHelpers
fixtures :all
render_views
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
end
describe "POST 'create'" do
describe "success" do
before(:each) do
#attr = { :email => "user#example.com",
:password => "foobar01", :password_confirmation => "foobar01", :display_name => "New User" }
end
it "should create a user" do
lambda do
post :create, :user => #attr
response.should redirect_to(root_path)
end.should change(User, :count).by(1)
end
end
end
describe "PUT 'update'" do
before(:each) do
#user = FactoryGirl.create(:user)
#user.confirm! # or set a confirmed_at inside the factory. Only necessary if you are using the confirmable module
sign_in #user
end
describe "Success" do
it "should change the user's display name" do
#attr = { :email => #user.email, :display_name => "Test", :current_password => #user.password }
put :update, :id => #user, :user => #attr
puts #user.errors.messages
#user.display_name.should == #attr[:display_name]
end
end
end
end
Now, when I run rspec spec I get (what I think) are weird results:
The "should create a user" test passes. The user count has increased by 1.
However, my "should change the user's display name" fails as follows:
1) Users::RegistrationsController PUT 'update' Success should change the user's display name
Failure/Error: #user.display_name.should == #attr[:display_name]
expected: "Test"
got: "Boyd" (using ==)
And the weird bit is that my statement:
puts #user.errors.messages
Renders the following message:
{:email=>["was already confirmed, please try signing in"]}
What's going on? The user is signed in! That's proved by the fact that the Rspec error returned the display_name of "Boyd". And why is it displaying a message looks as though it's related with account confirmation as opposed to updating a user's details?
Any help would be greatly appreciated!
This works. Thanks holtkampw for seeing what I wasn't! I put some extra code in there just to double check and everything is well!
it "should change the user's display name" do
subject.current_user.should_not be_nil
#attr = { :email => #user.email, :display_name => "Test", :current_password => #user.password }
puts "Old display name: " + subject.current_user.display_name
put :update, :id => subject.current_user, :user => #attr
subject.current_user.reload
response.should redirect_to(root_path)
subject.current_user.display_name == #attr[:display_name]
puts "New display name: " + subject.current_user.display_name
end
I've got a problem with my controller tests for my Courses Controller. It seems that devise is not signing in my user correctly. All the generated controller tests for this controller only are failing.
I create my user in users.rb using Factory-girl as per below...
FactoryGirl.define do
factory :user do
sequence :email do |n|
"test#{n}#email.com"
end
password "password"
password_confirmation "password"
end
end
Then in my courses_controller_spec.rb I simulate the login as per below..
require 'spec_helper'
describe CoursesController do
include Devise::TestHelpers
before(:each) do
##request.env["devise.mapping"] = Devise.mappings[:user]
user = Factory.create(:user)
user.toggle!(:admin)
sign_in user
end
describe "DELETE destroy" do
it "redirects to the courses list" do
course = Factory.create(:course)
delete :destroy, {:id => course.to_param}, valid_session
response.should redirect_to(courses_url)
end
end
And I get the output...
Failure/Error: response.should redirect_to(courses_url)
Expected response to be a redirect to <http://test.host/courses> but was a redirect to <http://test.host/users/sign_in>
Please note I've also used the following in my spec_helper.rb
config.include Devise::TestHelpers, :type => :controller
And I've tried it as per https://github.com/plataformatec/devise/wiki/How-To:-Controllers-and-Views-tests-with-Rails-3-%28and-rspec%29
In my request specs I can create the user and login using the below which works fine but I'd like to get all the controller tests working also
fill_in "Email", :with => user.email
fill_in "Password", :with => user.password
click_button "Sign in"
Any help here would be much appreciated.
I just figured this out myself. Remove the valid_session from your delete call. That seems to overwrite the session defined using the devise test helpers.
Everything else seems correct. Keep all the devise setup code and just change the delete line in your spec to:
delete :destroy, {:id => course.to_param}
I also kept in the following line in my before( :each ) block, which you commented out. Not sure what it does yet:
#request.env["devise.mapping"] = Devise.mappings[:user]