I am attempting to stub out an omniauth authentication hash to test my integration with RSpec. For some reason my User model is being fed an "Example User," that does not have all the info a regular signed in Google user would have.
This is the param given to User that is breaking the tests: {"provider"=>"default", "uid"=>"1234", "info"=>{"name"=>"Example User"}}
This is what it should be, and if I step to the next iteration with pry, it works:
{:provider=>"google",
:uid=>"12345678910",
:info=>{:email=>"limsammy1#gmail.com", :first_name=>"Sam", :last_name=>"Lim"},
:credentials=>{:token=>"abcdefg12345", :refresh_token=>"12345abcdefg", :expires_at=>Thu, 16 Nov 2017 15:27:23 -0700}}
Here is my spec:
require 'rails_helper'
def stub_omniauth
OmniAuth.config.test_mode = true
OmniAuth.config.mock_auth[:google] = OmniAuth::AuthHash.new({
provider: "google_oauth2",
uid: "12345678910",
info: {
email: "limsammy1#gmail.com",
first_name: "Sam",
last_name: "Lim"
},
credentials: {
token: "abcdefg12345",
refresh_token: "abcdefg12345",
expires_at: DateTime.now,
}
})
end
RSpec.feature "user logs in" do
scenario "using google oauth2 'omniauth'" do
stub_omniauth
visit root_path
expect(page).to have_link("Sign in with Google")
click_link "Sign in with Google"
expect(page).to have_content("Sam Lim")
expect(page).to have_content("Logout")
end
end
And here is my User model method:
def self.update_or_create(auth)
user = User.find_by(uid: auth[:uid]) || User.new
binding.pry
user.attributes = {
provider: auth[:provider],
uid: auth[:uid],
email: auth[:info][:email],
first_name: auth[:info][:first_name],
last_name: auth[:info][:last_name],
token: auth[:credentials][:token],
refresh_token: auth[:credentials][:refresh_token],
oauth_expires_at: auth[:credentials][:expires_at]
}
user.save!
user
end
I call that method in my sessions controller here:
def create
user = User.update_or_create(request.env["omniauth.auth"])
session[:id] = user.id
redirect_to root_path
end
I came across exactly this issue some days ago.
The problem is in def stub_omniauth.
You should change OmniAuth.config.mock_auth[:google] => OmniAuth.config.mock_auth[:google_oauth2]
Related
I'm testing confirmation method from a controller using RSpec, it seems like tests crush because I can't pass session variables when addressing the method.
Here's my spec:
RSpec.describe OauthCallbacksController, type: :controller do
describe 'POST #confirm_email' do
before do
request.env["devise.mapping"] = Devise.mappings[:user]
end
context 'confirms_email' do
let(:email) { 'test-email#test.ru' }
let(:confirm_email) { post :confirm_email, params: { email: email }, session: { auth: { uid: '12345', provider: 'facebook' } } }
it 'redirects to new_user_session_path' do
confirm_email
expect(response).to redirect_to new_user_session_path
end
it 'sends an email' do
expect { confirm_email }.to change{ ActionMailer::Base.deliveries.count }.by(1)
end
end
end
end
Method confirm_email:
def confirm_email
puts params[:email]
pending_user = User.find_or_init_skip_confirmation(params[:email])
if pending_user
aut = Authorization.where(provider: session[:auth]['provider'], uid: session[:auth]['uid'], linked_email: params[:email])
.first_or_initialize do |auth|
auth.user = pending_user
auth.confirmation_token = Devise.friendly_token[0, 20],
auth.confirmation_sent_at = Time.now.utc
end
if aut.save
OauthMailer.send_confirmation_letter(aut).deliver_now
redirect_to root_path, notice: "Great! Now confirm your email, we've sent you a letter!"
else
redirect_to root_path, alert: "Something went wrong. Please try again later or use another sign in method"
end
end
end
When I try to display session variables they are nil, In test.log it's obvious that there should be session variables are NULL-s.
[34mSELECT "authorizations".* FROM "authorizations" WHERE "authorizations"."provider" IS NULL AND "authorizations"."uid" IS NULL AND "authorizations"."linked_email" = $1 ORDER BY "authorizations"."id" ASC LIMIT $2[0m [["linked_email", "test-email#test.ru"], ["LIMIT", 1]]
I use Rails 5.2.2.1 and RSpec 3.8.
I've read that in Rails 5 there's a problem accessing sessions, I've already tried setting it with controller.session[:auth]['uid'] or
request.session[:auth]['uid'] but nothing
Something strange is happening in rails or rack but with a nested session, the keys are available as symbols. This should work
Authorization.where(provider: session[:auth][:provider], uid: session[:auth][:uid], linked_email: params[:email])
I managed to pass session parameters using request.env["omniauth.auth"] = mock_auth_hash(:facebook) and when instead of passing hash I've passed request to the session. It did the job.
Now my spec looks like this.
describe 'POST #confirm_email' do
before do
request.env["devise.mapping"] = Devise.mappings[:user]
request.env["omniauth.auth"] = mock_auth_hash(:facebook)
get :facebook
end
let(:confirm_email) { post :confirm_email, params: { email: 'mockuser#test.com'}, session: { auth: request.env["omniauth.auth"] } }
it 'redirects to root_path' do
confirm_email
expect(response).to redirect_to root_path
end
it 'sends an email' do
request.env["devise.mapping"] = Devise.mappings[:user]
request.env["omniauth.auth"] = mock_auth_hash(:facebook)
get :facebook
expect { confirm_email }.to change{ ActionMailer::Base.deliveries.count }.by(1)
end
end
am relatively new with rspec and testing,am trying to write a test for this code
#assessments = Assessment.where(assigned_user_id: current_user.id)
here is what i have tried so far,but am not sure if it's the right way,i was told controller spec is not advisable,hence i am trying to implement it using unit test.
spec_file
require 'rails_helper'
# Test suite for the assessment model
RSpec.describe Assessment, type: :model do
let(:admin) { User.new(email: 'rockcoolsaint#example.com', password: 'password', password_confirmation: 'password', role: :admin) }
let(:behavioral_specialist) { User.new(email: 'bs#example.com', password: 'password', password_confirmation: 'password', role: :behavioral_specialist) }
let(:behavioral_specialist_2) { User.new(email: 'bs2#example.com', password: 'password', password_confirmation: 'password', role: :behavioral_specialist) }
let(:bs_user_assessments) { Assessment.where(assigned_user_id: behavioral_specialist.id) }
describe 'Validations' do
it { should validate_presence_of(:title) }
it 'is valid with all attributes' do
expect(build(:assessment, user: admin, assigned_user: behavioral_specialist)).to be_valid
end
it 'is valid if assigned_user_id equals behavioral_specialist_id' do
bs_user_assessments.each do |assessment|
expect(assessment.assigned_user_id).to eq(behavioral_specialist.id)
end
end
it 'is valid if assigned_user_id does not equal behavioral_specialist_2_id' do
bs_user_assessments.each do |assessment|
expect(assessment.assigned_user_id).to_not eq(behavioral_specialist_2.id)
end
end
end
Hi i am using a Rails application with ruby-2.2.5 and rails 5 i am using rspec to test my controller. there is a callback in User model before_create :create_token i want to skip this callback in rspec.
spec/controllers/check_token_controller_spec.rb
# frozen_string_literal: true
require 'rails_helper'
describe CheckTokenController do
before do
#user = User.create!(fullname: 'saddam husain',
email: 'saddam#gmail.com',
password: 'pass',
password_confirmation: 'pass',token: 'xyz')
end
describe 'POST create' do
subject { post :create, params: params }
context 'when credentials are valid' do
let(:params) do
{ data: { attributes: { username: 'saddam#gmail.com', token: 'xyz' } } }
end
it { is_expected.to have_http_status(200) }
end
context 'when credentials are invalid' do
let(:params) do
{ data: { attributes: { username: 'saddam#gmail.com', token: '' } } }
end
it { is_expected.to have_http_status(401) }
end
end
end
i want to skip create_token callback. please help me how to skip.
A quick solution is using the RSpec#allow_any_instance_of method to stub the create_token for all classes.
before do
allow_any_instance_of(User).to receive(:create_token)
#user = User.create!(fullname: 'saddam husain', email: 'saddam#gmail.com', ...)
end
This isn't recommended (reference; please read the full section), and you probably need to skip a third-party code, or service integration inside create_token method. So you should mock this service instead of the create_token method. Here is a sample on how to do this: https://stackoverflow.com/a/19281316/1042324.
Let us know the content of your create_token method so we can help you better.
I asked a similar question before but I think I've gotten past my original error. Anyway I have a new fun failure that I'm having a blast trying to figure out (note the sarcasm). Here's my failure:
1) SessionsController#facebook_login should be valid
Failure/Error: get :facebook_login
NoMethodError:
undefined method `slice' for nil:NilClass
# ./app/models/user.rb:19:in `from_omniauth'
# ./app/controllers/sessions_controller.rb:22:in `facebook_login'
# ./spec/controllers/sessions_controller_spec.rb:96:in `block (3 levels) in <top (required)>'
sessions_controller_spec.rb
describe '#facebook_login' do
before(:each) do
valid_facebook_login_setup
request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:facebook]
get :facebook_login
end
it "should be valid" do
expect(response).to be_success
end
it "should set user_id" do
expect(session[:user_id]).to be_true
end
end
sessions_controller.rb
def facebook_login
if request.env['omniauth.auth']
user = User.from_omniauth(env['omniauth.auth'])
session[:user_id] = user.id
redirect_back_or root_path
else
redirect_to root_path
end
end
omniauth_test_helper.rb
module OmniAuthTestHelper
def valid_facebook_login_setup
if Rails.env.test?
OmniAuth.config.test_mode = true
OmniAuth.config.mock_auth[:facebook] = OmniAuth::AuthHash.new({
provider: 'facebook',
uid: '123545',
info: {
first_name: "Andrea",
last_name: "Del Rio",
email: "test#example.com"
},
credentials: {
token: "123456",
expires_at: Time.now + 1.week
}
})
end
end
def facebook_login_failure
OmniAuth.config.mock_auth[:facebook] = :invalid_credentials
end
end
spec_helper.rb
RSpec.configure do |config|
config.include FactoryGirl::Syntax::Methods
config.include Capybara::DSL
config.fixture_path = "#{::Rails.root}/spec/fixtures"
config.use_transactional_fixtures = true
config.infer_base_class_for_anonymous_controllers = false
config.order = "random"
config.include SessionTestHelper, type: :controller
config.include OmniAuthTestHelper, type: :controller
end
user.rb
class User < ActiveRecord::Base
def self.from_omniauth(auth)
where(auth.slice(:provider, :uid)).first_or_create do |user|
user.provider = auth.provider
user.uid = auth.uid
user.first_name = auth.info.first_name
user.last_name = auth.info.last_name
user.email = auth.info.email
user.password = auth.credentials.token
user.password_confirmation = auth.credentials.token
user.oauth_token = auth.credentials.token
user.oauth_expires_at = Time.at(auth.credentials.expires_at)
end
end
end
Any help would be really cool. Thanks guys!
Ok I left these tests pending but I finally came around to figuring this out. First, because it's a callback, it shouldn't be a controller test. It should be a request spec. So we're going to test that "/auth/facebook/callback" when given a mock will login a user.
spec/requests/user_sessions_request_spec.rb
require 'spec_helper'
describe "GET '/auth/facebook/callback'" do
before(:each) do
valid_facebook_login_setup
get "auth/facebook/callback"
request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:facebook]
end
it "should set user_id" do
expect(session[:user_id]).to eq(User.last.id)
end
it "should redirect to root" do
expect(response).to redirect_to root_path
end
end
describe "GET '/auth/failure'" do
it "should redirect to root" do
get "/auth/failure"
expect(response).to redirect_to root_path
end
end
Here's the rspec helper
spec/support/omni_auth_test_helper
module OmniAuthTestHelper
def valid_facebook_login_setup
if Rails.env.test?
OmniAuth.config.test_mode = true
OmniAuth.config.mock_auth[:facebook] = OmniAuth::AuthHash.new({
provider: 'facebook',
uid: '123545',
info: {
first_name: "Gaius",
last_name: "Baltar",
email: "test#example.com"
},
credentials: {
token: "123456",
expires_at: Time.now + 1.week
},
extra: {
raw_info: {
gender: 'male'
}
}
})
end
end
end
Don't forget to include the module in your spec_helper
I'm told this is the correct way to write a controller it block:
describe UsersController do
let(:user){ mock_model(User, id: 2, name: "Jimbo", email: 'jimbo#email.com', password: 'passwordhuzzah', password_confirmation: 'passwordhuzzah') }
describe 'PATCH #update' do
it "should fail in this case" do
User.should_receive(:find).with(user.id.to_s).and_return user
user.should_receive(:update_attributes).with({ "email" => user.email, "name" => user.name, "password" => user.password, "password_confirmation" => user.password_confirmation }).and_return true
patch :update, id: user.id, user: { email: user.email, name: user.name, password: user.password, password_confirmation: user.password_confirmation }
flash[:error].should == "could not update user"
response.status.should == 200
end
end
end
But I am surprised, because it doesn't seem DRY to me. Let's say I want to create two 'contexts' here. One where the user.update_attributes call returns false and returns true. Am I meant to simply copy paste two it blocks and tweak that one tiny argument?
describe UsersController do
let(:user){ mock_model(User, id: 2, name: "Jimbo", email: 'jimbo#email.com', password: 'passwordhuzzah', password_confirmation: 'passwordhuzzah') }
it "should pass in this case" do
User.should_receive(:find).with(user.id.to_s).and_return user
user.should_receive(:update_attributes).with({ "email" => user.email, "name" => user.name, "password" => user.password, "password_confirmation" => user.password_confirmation }).and_return true
patch :update, id: user.id, user: { email: user.email, name: user.name, password: user.password, password_confirmation: user.password_confirmation }
flash[:error].should == "updated user"
response.status.should == 302
end
it "should fail in this case" do
User.should_receive(:find).with(user.id.to_s).and_return user
user.should_receive(:update_attributes).with({ "email" => user.email, "name" => user.name, "password" => user.password, "password_confirmation" => user.password_confirmation }).and_return false
patch :update, id: user.id, user: { email: user.email, name: user.name, password: user.password, password_confirmation: user.password_confirmation }
flash[:error].should == "could not update user"
response.status.should == 200
end
end
end
I was hoping rspec would allow me to write this sort of structure:
describe 'PATCH #update' do
before {}
context 'when attributes can be updated' do
before {}
it "should set the flash" do end
it "should set the status" do end
end
context 'when attributes can\'t be updated' do
before {}
it "should set the status" do end
it "should set the flash" do end
end
end
Note the multiple it blocks for the same contexts, because isn't one expectation per it block a good practise because it allows you to see exactly what's not working? How are you meant to do this with mocks?