Setting roles through rolify in FactoryBot definition - ruby-on-rails

I am using devise, rolify and cancan. I'm also using RSpec with FactoryBot for testing. Right now I'm working on some tests and I want to define users with different roles for those tests. Here is my current guess for how to do that using FactoryBot:
FactoryBot.define do
factory :user do
name 'Test User'
email 'example#example.com'
password 'please'
password_confirmation 'please'
# required if the Devise Confirmable module is used
confirmed_at Time.now
factory :admin do
self.has_role :admin
end
factory :curator do
self.has_role :curator
end
factory :super_admin do
self.has_role :super_admin
end
end
end
Here are some of my tests which don't seem to be written correctly:
require 'spec_helper'
describe "Pages" do
subject { page }
before do
#newpage = FactoryBot.create(:page)
#newpage.save
end
context 'not logged in' do
it_behaves_like 'not admin'
end
context 'logged in' do
context 'as user' do
before do
#user = FactoryBot.create(:user)
sign_in #user
end
it_behaves_like 'not admin'
end
context 'as curator' do
before do
#curator = FactoryBot.create(:curator)
sign_in #curator
end
it_behaves_like 'not admin'
end
context 'as admin' do
before do
#admin = FactoryBot.create(:admin)
sign_in #admin
end
it_behaves_like 'admin'
end
context 'as super admin' do
before do
#super_admin = FactoryBot.create(:super_admin)
sign_in #super_admin
end
it_behaves_like 'admin'
end
end
end
When I run the specs I these are my errors:
1) Pages logged in as admin behaves like admin can show page
Failure/Error: Unable to find matching line from backtrace
NoMethodError:
undefined method `has_role=' for #<User:0x007f883384d178>
Shared Example Group: "admin" called from ./spec/requests/pages_spec.rb:41
# ./spec/requests/pages_spec.rb:37:in `block (4 levels) in <top (required)>'
2) Pages logged in as curator behaves like not admin can show page
Failure/Error: Unable to find matching line from backtrace
ArgumentError:
Factory not registered: curator
Shared Example Group: "not admin" called from ./spec/requests/pages_spec.rb:32
# ./spec/requests/pages_spec.rb:28:in `block (4 levels) in <top (required)>'
3) Pages logged in as super admin behaves like admin can show page
Failure/Error: Unable to find matching line from backtrace
ArgumentError:
Factory not registered: super_admin
Shared Example Group: "admin" called from ./spec/requests/pages_spec.rb:50
# ./spec/requests/pages_spec.rb:46:in `block (4 levels) in <top (required)>'
Note: FactoryBot was previously called FactoryGirl

I would rather use FactoryBot's after(:create) callback to create roles (also see this issue for Rolify).
Furthermore the method has_role? is used to check if a user has a specific role, to set a specific role you should use the add_role method.
FactoryBot.define do
factory :user do
name 'Test User'
email 'example#example.com'
password 'please'
password_confirmation 'please'
# required if the Devise Confirmable module is used
confirmed_at Time.now
factory :admin do
after(:create) {|user| user.add_role(:admin)}
end
factory :curator do
after(:create) {|user| user.add_role(:curator)}
end
factory :super_admin do
after(:create) {|user| user.add_role(:super_admin)}
end
end
end
Note: FactoryBot was previously called FactoryGirl

Related

Devise User testing with RSpec

I've integrated Devise with my RoR app and am now trying to test my Controllers, specifically the one that routes me to my root_url.
I've used this HOWTO on Devise's page to setup my admin/user Factories, but there is an additional component that is part of my user signup process, which is creating a Company.
So:
User: has_one :company
Company: has_many :users
The flow for a new user looks like this:
User signs up
User confirms account (via email) and is redirected to the login page
User logs in
User fills out Company information and submits
User is then redirected to Pages#home (which is my root_url)
Using Devise's HOWTO, I created a ControllerHelpers file within Support:
module ControllerHelpers
def login_user
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
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
end
end
I suspect there is something wrong with my User Factory since it doesn't seem like a Company is being created, but I'm so new to RSpec, I'm unsure.
FactoryGirl.define do
factory :user do
first_name "Test"
last_name "User"
full_name "Test User"
email "test#user.com"
phone_number "111-222-3333"
terms_accepted true
time_zone "Central Time (US & Canada)"
password "password"
password_confirmation "password"
confirmed_at Date.today
association :company
end
end
And I have a company.rb factory as well:
FactoryGirl.define do
factory :company do
id 1
name "ACME Test"
address_1 "123 Shady Lane."
address_2 "Suite 400"
city "Testville"
state "Test"
zip_code "12345"
has_payment_plan false
stripe_id "cus_34d434343e4e3e3"
locked false
end
end
My pages_controller_spec.rb is simple at this point:
require 'rails_helper'
RSpec.describe PagesController, :type => :controller do
describe "User: GET #home" do
login_user
it "signs in the user" do
expect(response).to render_template(:home)
end
end
end
This results in the following RSpec error:
1) PagesController User: GET #home signs in the user
Failure/Error: expect(response).to render_template(:home)
expecting <"home"> but was a redirect to <http://test.host/companies/new>
# ./spec/controllers/pages_controller_spec.rb:10:in `block (3 levels) in <top (required)>'
So, it's not even doing the render_template portion of my test?
UPDATE: Added Home Controller
controllers/pages_controller#home
def home
if current_user && current_user.company
verify_subscription
get_company_and_locations
get_network_hosts
get_network_hosts_at_risk
#network_hosts_snip = #network_hosts_at_risk.sort_by{ |h| -h.security_percentage }.first(5)
get_company_issues
#issues = #issues.sort_by{ |i| -i.cvss_score }.first(5)
#deferred_issues = #company.deferred_issues.last(5)
#deferred_hosts = #company.deferred_hosts.last(5)
else
redirect_to new_company_path
end
end
As we found out together in the chat... Your company was created but wasn't persisted to DB. It's because you had strategy: :build in your factory.
strategy: :build means that your association object will be created but won't be persisted to DB. To persist it you should use strategy: :create. Or in your use case you can replace association :company, strategy: :build with just company. FactoryGirl is smart enough to recognize it as an association which must be created and persisted.
And you need to set up FactoryGirl association between company and subscription the same way.

Failing uniqueness validation with FactoryGirl

I'm having problems with FactoryGirl, I'm using sequence to avoid duplicating fields, but validations are failing anyway.
Output:
1) CustomersController anonymous user GET #edit is redirected to signin when accessing edit form
Failure/Error: get :edit, id: create(:customer)
ActiveRecord::RecordInvalid:
Validation failed: Email has already been taken, Email has already been taken
# ./spec/controllers/customers_controller_spec.rb:25:in `block (4 levels) in <top (required)>'
# -e:1:in `<main>'
3) Customer public class methods executes its methods correctly #find_by_id_or_name finds customer by name
Failure/Error: let(:john) {create(:customer, name: 'John Doe X')}
ActiveRecord::RecordInvalid:
Validation failed: Email has already been taken, Email has already been taken
# ./spec/models/customer_spec.rb:25:in `block (3 levels) in <top (required)>'
# ./spec/models/customer_spec.rb:38:in `block (5 levels) in <top (required)>'
# -e:1:in `<main>'
Factories:
FactoryGirl.define do
factory :customer do
user
name {Faker::Name.name}
sequence(:mail) { |n| "person#{n}#example.com" }
address {Faker::Address.street_address}
phone {Faker::PhoneNumber.phone_number}
end
end
FactoryGirl.define do
factory :user do
sequence(:email) {|i| "example#{i}#example.com"}
password {Faker::Internet.password(10)}
end
end
These are the tests that are failing:
RSpec.describe Customer, type: :model do
describe "public class methods" do
let(:john) {create(:customer, name: 'John Doe X')}
let(:frank) {create(:customer)}
context "responds to its methods" do
it "responds to #find_by_id_or_name" do
expect(Customer).to respond_to(:find_by_id_or_name)
end
end
context "executes its methods correctly" do
context "#find_by_id_or_name" do
it "finds customer by name" do
customer = Customer.find_by_id_or_name('John Doe X')
expect(customer).to eq john
end
it "finds customer by id" do
customer = Customer.find_by_id_or_name(frank.id)
expect(customer).to eq frank
end
end
end
end
end
RSpec.describe CustomersController, type: :controller do
context "signed in user" do
before :each do
#user = create(:user)
end
describe "GET #edit" do
it "renders :edit view" do
get :edit, id: create(:customer).id
expect(response).to render_template(:edit)
end
end
describe "DELETE #destroy" do
before :each do
#customer = create(:customer, user: #user)
end
it "deletes record" do
expect {delete :destroy, id: #customer.id}.to change(Customer, :count).by(-1)
end
end
end
end
This is happening to me all over my app. I just copied some tests that apply to Customer.
Thanks
My problem was related to not appropiately configuring database_cleaner. The configuration in this post solves the problem: How can I clean my database between erroneous rspec specs?

Factory Girl not initiating secondary object

I have two Factory Girl objects on a single file.
spec/factories/users.rb
FactoryGirl.define do
factory :user do
first_name "Charlie"
last_name "Brown"
email "email#example.com"
password "charbar1234"
password_confirmation "charbar1234"
end
factory :admin do
first_name "Bob"
last_name "Marley"
email "bob#marley.com"
password "bobmarley1234"
password_confirmation "bobmarley1234"
admin true
end
end
When I call create(:user), my test runs fine. When I call create(:admin), I get the following error...
Failures:
1) admin accesses the database
Failure/Error: create(:admin)
NameError:
uninitialized constant Admin
# ./spec/features/admins/admin_spec.rb:5:in `block (2 levels) in <top (required)>'
Here is the test..
spec/features/admins/admin_spec.rb
require "rails_helper"
describe "admin" do
it 'accesses the database' do
create(:admin)
visit root_path
click_link "Log In"
fill_in "Email", with: "bob#marley.com"
fill_in "Password", with: "bobmarley1234"
click_button "Log In"
expect(current_path).to eq(admin_dashboard_path)
with 'h1' do
expect(page).to have_content 'Administration'
end
expect(page).to have_content 'Manage Users'
expect(page).to have_content 'Manage Articles'
end
end
You need to put the factory :admin inside of the factory :user.
It should look something like this:
FactoryGirl.define do
factory :user do
...
factory :admin do
admin true
end
end
end
See Factorygirl Admin Creation

Authenticated RSpec testing and Factory Girl

I'm writing basic tests for a simple CRUD rails application. I am using devise for authentication and Factory Girl for object creation whilst testing, after testing I am clearing the test.db with the Database Cleaner gem.
I am testing a controller with RSpec but need an admin user to be signed in for this to be a 'true' test.
So far I have been following documentation but I don't believe it is working.
I have a test which checks if the count has been changed by one:
describe "POST create" do
context "with valid attributes" do
it "creates a new room" do
expect{ post :create, room: FactoryGirl.attributes_for(:room) }.to change(Room,:count).by(1)
end
When I run the test suite I get the error:
expected #count to have changed by 1, but was changed by 0
From reading around it seems I need to setup authentication with my tests. To do this I have created relevant Factories:
# Devise User Class
factory :user do
email "basicuser#mvmanor.co.uk"
password "u"
password_confirmation "u"
admin false
customer false
end
# Admin
factory :admin, class: User do
email "basicadmin#mvmanor.co.uk"
password "a"
password_confirmation "a"
admin true
customer false
end
I have also created the relevant Devise mappings macros:
module UserControllerMacros
def login_admin
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:admin]
sign_in FactoryGirl.create(:admin) # Using factory girl as an example
end
end
def login_user
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
sign_in FactoryGirl.create(:user) # Using factory girl as an example
end
end
end
I'm not sure if I'm heading in the right direction with this. I certainly need my controller tests to be authenticated.
Before the first describe statement add this:
let!(:admin) { FactoryGirl.create(:admin) }
before { subject.stub(current_user: admin, authenticate_user!: true) }
it should stub your authentication.
And one little trick for your happiness: add in your spec_helper.rb anywhere within
RSpec.configure do |config|
...
end
block this code:
config.include FactoryGirl::Syntax::Methods
and now you don't need to preface all factory_girl methods with FactoryGirl, so instead of FactoryGirl.create you can write just create.

Using factory girl to create HABTM association

I've been trying now for hours to get factorygirl to create two factories - one for users, one for organizations.
But I don't seem to understand how I can reflect a 'has_and_belongs_to_many' relationship in factories, as soon as I try to create an organization and associate it with an admin user, I run into various error messages (depending on the approach I use).
My model seems to be working fine, my seed file populates the dev DB and all the associations are created.
Right now my files look like this:
user factory
FactoryGirl.define do
factory :user do
email 'example#example.com'
password 'password'
password_confirmation 'password'
after(:create) {|user| user.add_role(:user)}
factory :owner do
after(:create) {|user| user.add_role(:owner)}
end
factory :admin do
after(:create) {|user| user.add_role(:admin)}
end
factory :superadmin do
after(:create) {|user| user.add_role(:superadmin)}
end
end
end
Organization factory
FactoryGirl.define do
factory :organization do |f|
f.name "example"
f.website "www.aquarterit.com"
f.association :users, :factory => :admin
end
end
in my specs I test this:
describe Organization do
it "has a valid factory" do
FactoryGirl.create(:organization).should be_valid
end
it "is invalid without a name" do
FactoryGirl.build(:organization, name: nil).should_not be_valid
end
it "is associated with at least one admin user" do
FactoryGirl.create(:organization)
it { should have_and_belong_to_many(:users)}
end
end
all three tests are failing, here are the error message:
1) Organization has a valid factory
Failure/Error: FactoryGirl.create(:organization).should be_valid
NoMethodError:
undefined method `each' for #<User:0x007fadbefda688>
# ./spec/models/organization_spec.rb:7:in `block (2 levels) in <top (required)>'
2) Organization is invalid without a name
Failure/Error: FactoryGirl.build(:organization, name: nil).should_not be_valid
NoMethodError:
undefined method `each' for #<User:0x007fadc29406c0>
# ./spec/models/organization_spec.rb:11:in `block (2 levels) in <top (required)>'
3) Organization is associated with at least one admin user
Failure/Error: organization = FactoryGirl.create(:organization)
NoMethodError:
undefined method `each' for #<User:0x007fadc2a3bf20>
# ./spec/models/organization_spec.rb:15:in `block (2 levels) in <top (required)>'
Any help is as always very much appreciated!
Update
In theory the same thing that works for assigning roles to the user should work for assigning an admin to the organization. But if I change organizations.rb to
FactoryGirl.define do
factory :organization do
name "example"
website "www.aquarterit.com"
after(:create) {|organization| organization.add_user(:admin)}
end
end
I get following error (I do have gem shoulda installed):
1) Organization is associated with at least one admin user
Failure/Error: it { should have_and_belong_to_many(:users)}
NoMethodError:
undefined method `it' for #<RSpec::Core::ExampleGroup::Nested_1:0x007ff2395f9000>
# ./spec/models/organization_spec.rb:16:in `block (2 levels) in <top (required)>'
Looks like you are not assigning users correctly and not creating the :admin user properly. For this to work, you need to assign an array of users to organization.users. And, you need to populate that array with a User instance (this assumes you have a User factory named :admin).
factory :organization do
name "example"
website "www.aquarterit.com"
after(:create) {|organization| organization.users = [create(:admin)]}
end
I do it this way, questions and tests have a HABTM relationship so:
FactoryGirl.define do
factory :question do
question 'Some stupid question'
user nil
factory :question_with_test do
# factory_girl's dynamic attributes, ignore it and pass it to evaluator
transient do
test nil
end
after(:create) do |question, evaluator|
create(:question_tests, question: question, test: evaluator.test)
end
end
end
end
Now I can create a question with HABTM to the Test model:
let(:test) { FactoryGirl.create :test, user: user }
let(:question_test_1) { FactoryGirl.create :question_with_test, user: user, test: test }
The question_tests factory is very basic:
FactoryGirl.define do
factory :question_tests, class: 'QuestionTest' do
question
test
end
end

Resources