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
Related
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.
I'm trying to update a record in a request spec, but it's not updating. Doing it in real life works. Here is the spec:
describe "sessions" do
before do
#user = FactoryGirl.create(:user)
#api_key = FactoryGirl.create(:api_key)
end
it "is updated properly" do
put "/api/v1/users/#{#user.id}?user_email=#{#user.email}&auth_token=#{#user.authentication_token}", {user: {name: "New Name"}},{ "HTTP_AUTHORIZATION"=>"Token token=\"#{#api_key.access_token}\"" }
#user.name.should eq("New Name")
response.status.should be(201)
end
end
The above test fails with the error:
Failure/Error: #user.name.should eq("New Name")
expected: "New Name"
got: "nil"
(compared using ==)
Name is an optional parameter, so I just don't set it in the Factory. If I do set it the line says got: "Bill" for example.
and, for completeness, here are the factories:
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'
factory :admin do
after(:create) { |user| user.role = 'admin'; user.save }
end
end
end
FactoryGirl.define do
factory :api_key do
access_token "MyString"
end
end
You just need to reload #user after your PUT call, e.g. #user.reload.
Using FactoryBot, I'm having trouble creating a admin Factory in my specs because every user is assigned a default role of user in a before_create callback. This means that any role I assign a factory will be changed to user when the callback happens.
What I really want to do is something like this:
Inside my spec
admin = FactoryBot.create(:user)
admin.role = 'admin'
The second line, admin.role = 'admin' doesn't do anything. Any ideas?
I'm open to better ways of doing this as well.
There might be a way of reassigning the value to a FactoryBot (formerly FactoryGirl) instantiation, but RSpec negates the need:
describe User do
let(:user) { FactoryBot.create(:user) }
context 'when admin' do
let(:user) { FactoryBot.create(:user, admin: true) }
# ...
end
end
Try using a trait:
factory :user do
sequence(:username) { |n| "User ##{n}"}
role 'user'
trait :is_admin do
role 'admin'
end
end
Usage:
FactoryBot.create(:user, :is_admin)
Or eventually an after(:create):
factory :user do
sequence(:username) { |n| "user ##{n}"}
role 'user'
end
factory :user_admin, class: User do
after(:create) { |user| user.role = 'admin'; user.save } # don't know if the .save is necessary here
sequence(:username) { |n| "User Admin ##{n}"}
end
Just another way
# Steal some code from MrYoshiji at first.
factory :user do
sequence(:username) { |n| "User ##{n}"}
role 'user'
# Then a separate factory inside
factory :admin do
role 'admin'
end
end
# Use
FactoryBot.create(:admin)
My User model has the following action:
def release(idea)
if idea.status == "claimed"
idea.status = "available"
self.ideas.delete(idea)
end
end
Each user has_many ideas, and this is the way to release an idea and say "I don't want to be responsible for this idea anymore."
However, the current implementation lets one user release an idea owned by another user. I could easily solve this in the function itself by checking for idea.user_id but I'm trying to learn how to use CanCan and rolify... which is where the problem starts.
ability.rb says:
if user.has_role? :user
can :release, Idea, user_id: user.id
end
This fails in rspec:
Failures:
1) User manipulates ideas: can't change an idea he doesn't own
Failure/Error: let(:james) {create(:user)}
NoMethodError:
undefined method `find_or_create_by' for #<Rolify::Adapter::ResourceAdapter:0xabc7d98>
tl;dr: Should I be specifying abilities for User or for Idea? If User, how should I word the condition?
Thank you!
Edit: User factory:
FactoryGirl.define do
factory :user do
name Faker::Name.name
email Faker::Internet.email
provider "MyString"
uid {"user_#{rand(1000).to_s}" }
trait :admin do
after(:create) {|user| user.add_role(:admin)}
end
trait :guest do
after(:create) {|user| user.add_role(:guest)}
end
trait :authorized_user do
after(:create) do |user|
user.add_role(:user)
user.remove_role(:guest)
end
end
end
end
This is my current testing setup:
# spec/factories.rb
require 'factory_girl'
FactoryGirl.define do
# Roles
factory :user_role, :class => Role do
name 'User'
end
# Users
factory :user, :class => User do
sequence(:email) {|n| "email#{n}#example.com" }
password 'password'
password_confirmation 'password'
name 'Yuri Userington'
roles { |a| [a.association(:user_role)] }
end
# Instruments
factory :instrument, :class => Instrument do
title "Doobie Doo Instrument Title"
is_valid true
association :user, :factory => :user
end
# Sequences
sequence :email do
"email#{n}#factory.com"
end
end
# spec/controllers/instruments_controller_spec.rb
require 'spec_helper'
describe InstrumentsController do
before (:each) do
#instrument = FactoryGirl.create(:instrument)
#attr = FactoryGirl.attributes_for(:instrument)
#user = FactoryGirl.create(:user)
end
describe "GET index" do
it "assigns all instruments as #instruments" do
instrument = Instrument.new(#attr)
instrument.user = #user
instrument.save!
get :index
assigns(:instruments).should eq([instrument])
end
end
end
The result is that when i run my tests, i get the following errors in my output:
Failures:
1) InstrumentsController GET index assigns all instruments as #instruments
Failure/Error: #instrument = FactoryGirl.create(:instrument)
ActiveRecord::RecordNotFound:
Couldn't find Role with id=2
# ./app/models/user.rb:21:in `assign_role_after_sign_up'
# ./spec/controllers/instruments_controller_spec.rb:24:in `block (2 levels) in <top (required)>'
Based on that it seems like the roles association call in my :user factory is NOT being called -- what am i doing wrong here? Am i using this in a completely wrong way?
thank you!!
There is much to say here. Compare your code with the following to see how many lines or words were removed.
FactoryGirl.define do
# Sequences
sequence :email do |n|
"email#{n}#factory.com"
end
# Roles
factory :user_role, :class => Role do
name 'User'
end
# Users
factory :user do
email
password 'password'
password_confirmation 'password'
name 'Yuri Userington'
roles { |user| [Factory(:user_role)] } #many to many
end
# Instruments
factory :instrument, :class => Instrument do
title "Doobie Doo Instrument Title"
is_valid true
association :user #one-to-one or one-to-many
end
end
And in your tests:
describe InstrumentsController do
before (:each) do
#user = Factory(:user)
end
describe "GET index" do
it "assigns all instruments as #instruments" do
instrument = Factory(:instrument, :user => #user)
get :index
assigns(:instruments).should eq([instrument])
end
end
end
Moreover:
I personally prefer testing controller with mocks and stubs
I use let instead of instance variables and before_filter
I had a similar issues and I used a callback to assign roles like this:
Factory.define :user_with_admin_role, :parent => :user do |user|
user.after_create {|instance| instance.roles << Factory(:admin_role) }
end
So I think you should be able to do something akin to that:
# Users
factory :user, :class => User do
sequence(:email) {|n| "email#{n}#example.com" }
password 'password'
password_confirmation 'password'
name 'Yuri Userington'
after_create {|user| user.roles << Factory(:user_role) }
end
That is completely untested, so you may need to tweak things around.