Create users in Factory Girl with OmniAuth? - ruby-on-rails

I am currently creating an application that uses OmniAuth to create and authenticate users. I am encountering problems during testing due to Factory Girl being unable to generate users without OmniAuth.
I have several different ways to get factory girl to create users with omniauth but none have been successful.
I have added the following 2 lines to my spec_helper file
OmniAuth.config.test_mode = true \\ allows me to fake signins
OmniAuth.config.add_mock(:twitter, { :uid => '12345', :info => { :nickname => 'Joe Blow' }})
current factories.rb
FactoryGirl.define do
factory :user do
provider "twitter"
sequence(:uid) { |n| "#{n}" }
sequence(:name) { |n| "Person_#{n}" }
end
end
The following test currently fails because no user is being generated
let(:user) { FactoryGirl.create(:user) }
before { sign_in user }
describe "registering" do
it "should increment" do
expect do
click_button 'register'
end.to change(user.rounds, :count).by(1)
end
How should I change my factories/tests in order to get Factory Girl to create test users with OmniAuth?
Edit: I used the RailsCast guide to setup Omniauth,
#create function inside user.rb
def self.create_with_omniauth(auth)
create! do |user|
user.provider = auth["provider"]
user.uid = auth["uid"]
user.name = auth["info"]["name"]
end
end
hopefully also useful
#create inside the session_controller
def create
auth = request.env["omniauth.auth"]
user = User.find_by_provider_and_uid(auth["provider"], auth["uid"]) || User.create_with_omniauth(auth)
session[:user_id] = user.id
redirect_to root_url, :notice => "Signed in!"
end

Did you remember to do the following somewhere in the test setup?
request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:twitter]
If you did, is it possible the user's UID doesn't match the mock uid?
You can try changing the factory definition from sequence(:uid) { |n| "#{n}" } to uid '12345'.

Related

Why does my test return a nil class error on an attribute it shouldn't?

I am trying to write a test for my InvitationsController#Create.
This is a POST http action.
Basically what should happen is, once the post#create is first executed, the first thing that needs to do is we need to check to see if a User exists in the system for the email passed in via params[:email] on the Post request.
I am having a hard time wrapping my head around how I do this.
I will refactor later, but first I want to get the test functionality working.
This is what I have:
describe 'POST #create' do
context 'when invited user IS an existing user' do
before :each do
#users = [
attributes_for(:user),
attributes_for(:user),
attributes_for(:user)
]
end
it 'correctly finds User record of invited user' do
post :create, { email: #users.first[:email] }
expect(response).to include(#users.first[:email])
end
end
end
This is the error I get:
1) Users::InvitationsController POST #create when invited user IS an existing user correctly finds User record of invited user
Failure/Error: post :create, { email: #users.first[:email] }
NoMethodError:
undefined method `name' for nil:NilClass
##myapp/gems/devise-3.2.4/app/controllers/devise_controller.rb:22:in 'resource_name'
# #myapp/gems/devise_invitable-1.3.6/lib/devise_invitable/controllers/helpers.rb:18:in 'authenticate_inviter!'
# #myapp/gems/devise_invitable-1.3.6/app/controllers/devise/invitations_controller.rb:67:in 'current_inviter'
# #myapp/gems/devise_invitable-1.3.6/app/controllers/devise/invitations_controller.rb:71:in 'has_invitations_left?'
I am using FactoryGirl and it works perfectly, in the sense that it returns valid data for all the data-types. The issue here is how do I get RSpec to actually test for the functionality I need.
Edit 1
Added my :user factory:
FactoryGirl.define do
factory :user do
association :family_tree
first_name { Faker::Name.first_name }
last_name { Faker::Name.last_name }
email { Faker::Internet.email }
password "password123"
password_confirmation "password123"
bio { Faker::Lorem.paragraph }
invitation_relation { Faker::Lorem.word }
# required if the Devise Confirmable module is used
confirmed_at Time.now
gender 1
end
end
It seems you're using Devise which require you to be logged in before going to the next step. On your error, Devise cannot get the same of your inviter because he's not logged.
Your test should be like this:
describe 'POST #create' do
context 'when invited user IS an existing user' do
before :each do
#users = [
attributes_for(:user),
attributes_for(:user),
attributes_for(:user)
]
#another_user = FactoryGirl.create(:user_for_login)
sign_in #another_user
end
it 'correctly finds User record of invited user' do
post :create, { email: #users.first[:email] }
expect(response).to include(#users.first[:email])
end
end
end
Example for FactoryGirl model for Devise
factory :user_for_login, class: User do |u|
u.email 'admin#myawesomeapp.com'
u.password 'password'
u.password_confirmation 'password'
u.name "MyName"
end
Of course, you need to add as much data as your validators want.. Basically for Devise you need email, password and password_confirmation. In you case, it seems you also need name.

FactoryGirl: Change an attribute after FactoryGirl.create is called

In my user model, all users are assigned the role of user in a before_create callback. So I'm having a lot of trouble creating an admin user to use in some tests. Here is what I've tried, which is not working:
require 'spec_helper'
describe "Exercises" do
describe "GET /Exercises" do
it "gives the expected status code." do
sign_in_as_valid_user
#user.role = 'admin'
get exercises_path
response.status.should be(200)
end
for completeness, here is the method that is called:
module ValidUserRequestHelper
def sign_in_as_valid_user
FactoryGirl.create :program
#user ||= FactoryGirl.create :user
post_via_redirect user_session_path, 'user[email]' => #user.email, 'user[password]' => #user.password
end
end
and the factory:
FactoryGirl.define do
sequence :email do |n|
"test#{n}#vitogo.com"
end
factory :user do
email
password '12345678'
password_confirmation '12345678'
goal_id 1
experience_level_id 1
gender 'Female'
end
end
I'm just trying to change the role in the specific tests where it matters.
Any ideas how to do this? It's been driving me crazy. Thanks in advance!
I then edited my users Factory to create an Admin Factory that inherited from my User Factory, then assigned the admin role in an after(:create) callback like this:
factory :user do
email
password '12345678'
password_confirmation '12345678'
gender 'Male'
factory :admin do
after(:create) { |user| user.role = 'admin'; user.save }
end
end
Try wrapping the #user in a method, something like this in the ValidUserRequestHelper
def current_user
#user
end
Then calling current_user.role = 'admin' in your specs

Testing a rails controller method that is designed to change data of a variable

I have the following code in the controller:
# guest to user sign up view. Method that prepares a guest to become a user by emptying it's generic
#e-mail address.
def guest_signup
if !current_user.guest
redirect_to root_url
end
#user = current_user
#user.email = ""
end
This controller just makes sure that the outcome (a form) doesn't have a generic e-mail address in an input field that the user gets assigned when he is using the application as guest.
I am trying to write an rspec test for it and I have no idea how to properly do it... I know this may sound like development-driven testing rather than the opposite but I need an idea.
Currently I have this that doesn't work:
require 'spec_helper'
describe UsersController do
describe "Guest Signup" do
it "should prepare guest with random e-mail user for signup form, emptying the e-mail" do
current_user = User.create(:email => "guest_#{Time.now.to_i}#{rand(99)}#example.com", :password => "#{Time.now.to_i}#{rand(99999999)}", :guest => true)
get :guest_signup, :user => #user.id
expect(#user.email).to eq ('')
end
end
end
How is #user assigned here? Presumably after the guest_signup method is called. Since #user is referenced in the call to guest_signup, you have an order of operations problem here.
Maybe you should be calling:
get :guest_signup, :user => current_user.id
describe UsersController do
describe 'Guest Signup' do
let(:user) { mock.as_null_object }
before(:each) { controller.stub(:current_user) { user } }
context 'when guest does not exist' do
before(:each) { user.stub(:guest) { false } }
it 'redirects to root path' do
get :guest_signup
response.should redirect_to root_path
end
end
context 'when guest exists' do
before(:each) { user.stub(:guest) { true } }
it 'should prepare guest with random e-mail user for signup form, emptying the e-mail' do
get :guest_signup
assigns(#user).should == user
assigns(#email).should be_empty
end
end
end
end

Adding roles to an OmniAuth log in so that Cucumber doesn't fail a test

I've been working on an app for learning purposes which includes OmniAuth for Facebook logins, Cucumber for BDD and CanCan & Rolify for permissions and roles. No Devise is used so far. I'm trying to write a test that involves logging a user with admin role and then visiting a restricted path. Also, users that have been created with OmniAuth have simple attributes: If the user has been confirmed to use the site, he/she will have confirmed: 1 and confirmation_token: nil; otherwise it will be confirmed: 0 and confirmation_token: . The idea actually works if I'm not in a Cucumber environment, but inside it, the page gives me a CanCan::AccessDenied error. Oh, and I've also set OmniAuth test mode, that works fine, env["omniauth.auth"] returns a proper mock hash.
This is my test (I'm still learning so bear with me)
#omniauth_test
Given /^I am logged as an admin$/ do
#ensure no session is active
visit '/signout'
#user = FactoryGirl.create(:user, confirmed: 1, confirmation_token: nil)
#user.add_role :admin
visit root_url
click_link "log_in" #means going to '/auth/facebook/callback'
end
And /^I check the user list$/ do
visit users_path #fails
end
This is my Factory for user, nothing complicated:
FactoryGirl.define do
factory :user do |u|
u.email 'test#example.com'
end
end
This is my SessionsController:
class SessionsController < ApplicationController
def create
reset_session
service = Service.from_omniauth(env["omniauth.auth"])
session[:user_id] = service.user.id
session[:service_id] = service.id
session[:expires_at] = 5.minutes.from_now
if service.user.confirmed == 0
redirect_to edit_user_path(service.user)
elsif service.user.confirmed == 1
if service.user.has_role? :member
redirect_to root_url
elsif service.user.has_role? :admin
redirect_to users_path
else
redirect_to root_url, :notice => "Work in progress!"
end
end
end
And finally, Service.rb:
class Service < ActiveRecord::Base
attr_accessible :user_id, :provider, :uid, :name, :token, :updated_at
validates_uniqueness_of :uid, :scope => [:provider]
belongs_to :user
def self.from_omniauth(auth)
where(auth.slice(:provider, :uid)).first_or_initialize.tap do |service|
if !service.user
user = User.create(:email => auth.info.email)
service.user = user
#for some reason, instance variable #user created by FactoryGirl is nil at this point (test env)
user.add_role :signedup
end
service.provider = auth.provider
service.uid = auth.uid
service.name = auth.info.name
service.token = auth.credentials.token
service.save!
end
end
What I would like is to somehow use the OmniAuth hash and add the admin role and the confirmed attributes only for test mode, without messing too much with the production code (if possible), maybe adding helper methods to env.rb, and logging in with that user.
Silly me. This did the trick:
#omniauth_test
Given /^I am logged as an admin$/ do
#user = FactoryGirl.create(:user, confirmed: 1, confirmation_token: nil)
#user.add_role :admin
#service = FactoryGirl.create(:service, user: #user, uid: '1234567', provider: 'facebook')
end

Rspec Factory issue- conceptual problem?

I'm trying to test a case in our Ruby on Rails system where we lock a user out after x failed login attempts. The issue I'm having is trying to create a user has reached the number that 'locks' his account. I am using Factories to create a user like so-
Factory.define :locked_user, :class => User do |user|
user.name "Test Lock"
user.email "lock#lock.com"
user.password "blah1234"
user.password_confirmation "blah1234"
user.login_count 5
end
Where 5 is the 'magic number'. When I try to use something like
#user = Factory(:locked_user)
It creates a user in the database- but newly created users always have login_count set to zero, so it just logs him in the test. When I try the .build method like so
#user = Factory.build(:locked_user)
It sets a user with login_count = 5 like I want, but then doesn't see the user as valid and won't try to log them in (ie, it gives us the 'bad user/password' error rather then 'right user/password but you are locked out' error). I guess I'm missing something here to get RSpec to pick up the fact that this is valid user but the account should be locked. Can someone help set me straight? Below is the entire desribe block-
describe "with locked account" do
before(:each) do
#user = Factory.build(:locked_user)
#attr = { :email => #user.email, :password => #user.password}
end
it "should not allow signin with locked account" do
post :create, :session => #attr
flash.now[:error].should =~ /Invalid user locked out/i
end
end
I would recommend you either set the login_count after creating the user, or stub the method that tells you if a user login is locked.
For instance, use update_attribute to force the login_count after the user has been created:
before(:each) do
#user = Factory(:user)
#user.update_attribute(:login_count, 5)
#attr = { :email => #user.email, :password => #user.password}
end
Or use stubs to stub out the locked_login?, or equivalent method:
before(:each) do
#user = Factory(:user)
#user.stub(:locked_login?).and_return(true)
#attr = { :email => #user.email, :password => #user.password}
end

Resources