Fixtures with polymorphic association not working - ruby-on-rails

I'm trying to implement the Rolify gem but have trouble adding fixtures with a scope for it. The last line of the (model) test below fails because currently the moderator role seems to be given to #user globally instead of only for organization one. The fixtures below aren't using resource_id and resource_type, which are mentioned in the gem documentation for fixtures, but I'm not sure how to use them. How should I set the scope for the moderator role to only organization one?
roles.yml
moderator:
id: 1
resource: one (Organization)
users.yml
one:
email: example#example.com
roles: moderator, organizations(:one) # I was hoping this would set the scope of the role to organization one but it isn't (seems to set the role globally).
test.rb
def setup
#moderator_role = roles(:moderator)
#organization1 = organizations(:one)
#organization2 = organizations(:two)
#user = users(:one)
end
test "should be moderator if fixtures correct" do
assert_equal #user.has_role?('moderator'), true
assert_equal #user.has_role?(:moderator, #organization1), true
assert_equal #user.has_role?(:moderator, #organization2), false # This line fails
end
Update: I also tried the code below. But still the test fails.
roles.yml
moderator:
name: :moderator
resource: one (Organization)
users.yml
one:
organization: one
roles: moderator, organizations(:one)
organizations.yml
one:
name: "Company A"
test.rb
def setup
#moderator_role = roles(:moderator)
#organization1 = organizations(:one)
#organization2 = organizations(:two)
#user = users(:one)
end
test "should be moderator if fixtures correct" do
assert_equal #user.has_role?('moderator'), true # This line fails
assert_equal #user.has_role?(:moderator, #organization1), true # This line also fails
assert_equal #user.has_role?(:moderator, #organization2), false
end

I found out that with the code in the update, the test does pass if I run it as a users controller test or integration test, instead of as a model test. So I guess I was just running it as the wrong type of test.

Related

RSpec PostgreSQL results not consistent running all/single test

I've got an issue with testing my application with an PostgreSQL database. In our current setup we use PostgreSQL for our production environment and SQLite3 for our development and testing environment. Because this can lead to unexpected behavior the development and testing enviroments need to switch to PostgreSQL.
Switching the testing environment is the toughest part. Some tests run correctly, when running one single spec file, but fail when running all tests together using Guard. When running test manually in my development it behaves expected.
Here's an exapmle. The test that is failing is the last one where I'm switching to the second user and test if can the #items contain anything. It resturn an empty an empty array.
# /spec/controllers/materials_controller_spec.rb
describe MaterialsController do
def create_users
#user_1 = FactoryBot.create(:user)
#user_2 = FactoryBot.create(:user)
#admin = FactoryBot.create(:super_admin)
#private_group = Group.find_or_create_by(name: "Private")
#shared_group = Group.find_or_create_by(name: "Shared")
#project_group = Group.find_or_create_by(name: "Project")
#project = FactoryBot.create(:project)
#user_1.projects << #project
#user_2.projects << #project
sign_in #user_1
end
def create_my_private_material
#my_private_material = FactoryBot.create(:material, user: #user_1, group: #private_group, name: "Test01")
end
def create_my_private_material_2
#my_private_material_2 = FactoryBot.create(:material, user: #user_1, group: #private_group)
end
def create_your_private_material
#your_private_material = FactoryBot.create(:material, user: #user_2, group: #private_group)
end
def create_my_project_material
#my_project_material = FactoryBot.create(:material, user: #user_1, group: #project_group, project: #project)
end
def create_your_shared_material
#your_shared_material = FactoryBot.create(:material, user: #user_2, group: #shared_group)
end
def create_all_materials
create_my_private_material
create_my_private_material_2
create_your_private_material
create_my_project_material
create_your_shared_material
end
describe "GET #index" do
before do
create_users
create_all_materials
end
context "as material owner" do
it "renders the index template" do
get :index
expect(assigns(:items)).to contain_exactly(#my_project_material, #my_private_material_2, #my_private_material)
expect(response).to render_template("index")
end
end
context "as being the owner and a current project set using the project scope" do
before do
#user_1.current_project_id = #project.id
#user_1.save
end
it "should assing only the the materials the belong to the current project" do
get :index, params: { scope: 'project' }
expect(assigns(:items)).to contain_exactly(#my_project_material)
end
end
context "as not being the material owner and no default project" do
before do
sign_out #user_1
sign_in #user_2
end
it "filters out the materials based on group and project" do
get :index
expect(assigns(:items)).to contain_exactly(#your_private_material, #your_shared_material)
end
end
context "as not being the owner, with a current project and using the project scope" do
before do
#user_2.current_project_id = #project.id
#user_2.save
sign_out #user_1
sign_in #user_2
end
it "should assign only the materials the belong to the project" do
get :index, params: { scope: 'project', project_id: #project.id }
expect(assigns(:items)).to contain_exactly(#my_project_material)
end
end
end
I've done some debugging and found a couple of things:
The error in the tests are consistent when running all the tests.
When adding DatabaseCleaner.clean_with :truncation in the before block seems to fix some tests.
Changing the number connections allowed to the test database to 1 does work for the controller tests but is a problem in my feature tests because it somehow seems to require at least to connections.
So the problem seems to be related to the advantages to a PostgreSQL database being able to handle multiple requests. But what I don't understand that running one single test would pass but running all tests would fail some test. I've searched around on the internet to find similar related issue's but could find any good post or question.
If needed I can post different test that are failing as well but I think it's all related to the same issue.
Any suggestions?

RSpec ActiveRecord::RecordInvalid: Validation failed: Email has already been taken despite having sequence in FactoryBot

I have a model called "availabilities" in my Rails application that allows vendors to set their availability (i.e. their work hours). Thus, availabilities belong_to vendors and belong_to users, and a user has_many vendors and a vendor has_many availabilities.
I've been trying to create Rspec tests for my availability#destroy action. The specific test I refer to is:
#spec/controllers/availabilities_controller_spec.rb
require 'rails_helper'
RSpec.describe AvailabilitiesController, type: :controller do
describe "availabilities#destroy action" do
it "should allow a user who created the availability to destroy it"
availability = FactoryBot.create(:availability)
sign_in availability.user
delete :destroy, params: { id: availability.id, vendor_id: availability.vendor_id}
availability = Availability.find_by_id(availability.id)
expect(availability).to eq nil
end
end
end
When I run this test however, I receive the following error:
"An error occurred while loading ./spec/controllers/availabilities_controller_spec.rb.
Failure/Error: user = FactoryBot.create(:user)
ActiveRecord::RecordInvalid:
Validation failed: Email has already been taken"
However, I use factory bot for my factories, and I have my user factory to run as a sequence (see below):
FactoryBot.define do
factory :user do
sequence :email do |n|
"dummyEmail#{n}#gmail.com"
end
password "secretPassword"
password_confirmation "secretPassword"
confirmed_at Time.now
end
end
How can the email already be taken? What could explain this error?
I recommend you to use Faker along with FactoryBot. It will give your more flexibility and eliminate the need to do this sequence trick. Faker generates fake data with ease.
In any way, use database_cleaner to clean your test environment database after each test. You'll only need to set this is up:
# ./spec/rails_helper.rb
# start by truncating all the tables but then use the faster transaction strategy the rest of the time.
config.before(:suite) do
DatabaseCleaner.clean_with(:truncation)
DatabaseCleaner.strategy = :transaction
end
# start the transaction strategy as examples are run
config.around(:each) do |example|
DatabaseCleaner.cleaning do
example.run
end
end

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.

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.

Ruby on Rails functional test with Devise authentication

I'm searching for a solution for a weird problem. I have a controller, that needs authentication (with the devise gem). I added the Devise TestHelpers but i can't get it working.
require 'test_helper'
class KeysControllerTest < ActionController::TestCase
include Devise::TestHelpers
fixtures :keys
def setup
#user = User.create!(
:email => 'testuser#demomailtest.com',
:password => 'MyTestingPassword',
:password_confirmation => 'MyTestingPassword'
)
sign_in #user
#key = keys(:one)
end
test "should get index" do
get :index
assert_response :success
assert_not_nil assigns(:keys)
end
test "should get new" do
get :new
assert_response :success
end
test "should create key" do
assert_difference('Key.count') do
post :create, :key => #key.attributes
end
assert_redirected_to key_path(assigns(:key))
end
test "should destroy key" do
assert_difference('Key.count', -1) do
delete :destroy, :id => #key.to_param
end
assert_redirected_to keys_path
end
end
And i get the following output in my "rake test" window:
29) Failure:
test_should_create_key(KeysControllerTest) [/test/functional/keys_controller_test.rb:29]:
"Key.count" didn't change by 1.
<3> expected but was
<2>.
30) Failure:
test_should_destroy_key(KeysControllerTest) [/test/functional/keys_controller_test.rb:37]:
"Key.count" didn't change by -1.
<1> expected but was
<2>.
31) Failure:
test_should_get_index(KeysControllerTest) [/test/functional/keys_controller_test.rb:19]:
Expected response to be a <:success>, but was <302>
32) Failure:
test_should_get_new(KeysControllerTest) [/test/functional/keys_controller_test.rb:25]:
Expected response to be a <:success>, but was <302>
Can someone tell my, why devise doesn't authenticate? I'm using the exact same procedure for an AdminController and it works perfect.
Are you using Devise with confirmable? In this case, create is not enough and you need to confirm the user with #user.confirm!
Second, why do you create the user in the functional test? Declare your users in the fixture like this (confirmed_at if you require confirmation only):
test/fixtures/users.yml:
user1:
id: 1
email: user1#test.eu
encrypted_password: abcdef1
password_salt: efvfvffdv
confirmed_at: <%= Time.now %>
and sign them in in your functional tests with:
sign_in users(:user1)
Edit: I just saw, that in my app the Devise-Testhelpers are declared in test/test-helpers.rb and I don't know if this makes a difference, maybe you want to try:
ENV["RAILS_ENV"] = "test"
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
class ActionController::TestCase
include Devise::TestHelpers
end
class ActiveSupport::TestCase
# Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
#
# Note: You'll currently still have to declare fixtures explicitly in integration tests
# -- they do not yet inherit this setting
fixtures :all
# Add more helper methods to be used by all tests here...
end
This took me some time to figure out but it turns out the answer is really simple.
The point is that, in your fixtures file (users.yml), you need to make sure the user is 'confirmed' (assuming that you specified "confirmable" in your User model). So, for instance, put this in your users.yml:
user_one:
confirmed_at: 2015/01/01
That's all, no need to specify other fields (email, encrypted password, etc).
Now in your controller test (e.g. in 'setup' or in 'before') you simply code:
sign_in users(:user_one)
And then it should just work!
I had a similar issue (but using FactoryGirl, rather than Fixtures) and was able to resolve it by simply using FactoryGirl.create(:user) rather than FactoryGirl.build(:user).
Evidently, Devise requires the user to have been persisted to the Database, for everything to work properly.

Resources