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?
Related
I have referred various answers on StackOverflow but I still haven't been able to resolve this issue. I am sharing the code for my controllers_test.rb and along with that the screenshot. Every time I run rails test, the following error is shown. I have made sure that console is close. In case there is any other source code needs to be share please do let me know.
require "test_helper"
class CategoriesControllerTest < ActionDispatch::IntegrationTest
setup do
#category = Category.create(name: "Sports")
end
test "should get index" do
get categories_url
assert_response :success
end
test "should get new" do
get new_category_url
assert_response :success
end
test "should create category" do
assert_difference('Category.count') do
post categories_url, params: { category: { name: "Travel" } }
end
assert_redirected_to category_url(Category.last)
end
test "should show category" do
get category_url(#category)
assert_response :success
end
end
test_helper.rb
class ActiveSupport::TestCase
# Run tests in parallel with specified workers
parallelize(workers: :number_of_processors, with: :threads)
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
fixtures :all
# Add more helper methods to be used by all tests here...
end
Using RSPEC and DEVISE, I'm able to sign in a user to run my tests. However, I've recently come across a problem with my method because using before :each is causing multiple users to be created, which causes my tests to break.
Should I change my method of testing? Or change my method of signing in a user?
campaign_controller_spec.rb
before :each do
#user = FactoryGirl.create(:user)
sign_in :user, #user
#business = FactoryGirl.create(:business, user: #user)
end
describe "GET #index" do
before :each do
FactoryGirl.create(:active_campaign, business: #business)
FactoryGirl.create(:inactive_campaign, business: #business)
end
it "no status or type filter" do
get :index # <- LINE 22 OF ERROR
expect(assigns(:campaigns).size).to eq 2
end
it 'status filter' do
get :index, status: 'active'
expect(assigns(:campaigns).size).to eq 1
end
end
campaigns_controller.rb
def index
# Get current user's campaigns
#campaigns = current_user.business.campaigns
# Filter by status (constraints are in routes)
#campaigns = #campaigns.send(params[:status]) unless params[:status] == 'all' # <- LINE 7 OF ERROR
end
The problem is that all of the campaigns belong to the business with belong to USER-1 in the spec, but in the controller, the campaigns belong to another user (because several are being created) which doesn't have any campaigns associated.
The exact error is:
1) CampaignsController [Signed in] GET #index no status or type filter
Failure/Error: get :index
TypeError:
nil is not a symbol nor a string
# ./app/controllers/campaigns_controller.rb:7:in `index'
# ./spec/controllers/campaigns_controller_spec.rb:22:in `block (4 levels) in <top (required)>'
Small Update
I DO have the Database Cleaner gem installed, which should clear the DB between tests. Here is the config (just in case it is incorrect):
#DatabaseCleaner
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
When using factory_girl you'll want to use something like the database_cleaner gem to clean your database between tests. Check out:
https://github.com/DatabaseCleaner/database_cleaner#user-content-rspec-example
EDIT:
Realized that your error isn't caused by what you think it's caused. It's because you're doing a .send(params[:status]) when params[:status] is nil because it's not passed in on the call to get :index... so once .send(nil) is invoked you get the stack trace.
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.
Following instructions from that Devise How-To page I'm trying to rebuild all my rspec test to bypass user signin process.
There are 2 methods that can be used for that:
sign_in from Devise - which can't be used with feature tests (Capybara)
login_as from Warden (Devise is build on top of it)
1st method at the first shot worked (all test passes) except those with Capybara so I decided to leave it for now.
2nd gives me some weird results - all passes except the first one (any which I place as the first in the file). It fails when I run just one of them. I've checked it with binding.pry and it fails as the user is not logged in and it redirects to the login page. Somehow first test triggers something that makes all the rest passing. I have no clue what's going on here.
I was using around hook before but it behaves really weird so I've changed that to set of before and after (at the same time it works much faster as it creates just one user at the begging rather than for every test). This is how it looks like now:
require 'spec_helper'
describe AlbumsController do
let(:album) { create(:album) }
before(:all) do
#user = create :user
end
before(:each) do
login_and_switch_schema #user
end
after(:all) do
destroy_users_schema #user
destroy_user #user
end
describe "GET #new" do
before { get :new }
it { expect(response).to render_template :new }
end
describe "GET #edit" do
before { get :edit, id: album }
it { expect(response).to render_template :edit }
end
...
and I've defined that helpers:
Warden.test_mode!
def login_and_switch_schema(user)
##request.env["devise.mapping"] = Devise.mappings[:user]
#sign_in :user, user
login_as(user, scope: :user)
Apartment::Database.switch(user.username)
end
def destroy_users_schema(user)
Apartment::Database.drop(user.username)
Apartment::Database.reset
end
def destroy_user(user)
User.destroy(user)
end
I would like to ask you for help.
I would try moving your before(:all) and after(:all) code into the before(:each) and after(:each). :all doesn't play well with let, DatabaseCleaner or in giving you a predictable order of execution for the first test executed.
I'm trying to get RSpec working for a simple scaffolded app, starting with the rspec scaffold tests.
Per the devise wiki, I have added the various devise config entries, a factory for a user and an admin, and the first things I do in my spec controller is login_admin.
Weirdest thing, though... all my specs fail UNLESS I add the following statement right after the it ... do line:
dummy=subject.current_user.inspect
(With the line, as shown below, the specs pass. Without that line, all tests fail with the assigns being nil instead of the expected value. I only happened to discover that when I was putting some puts statements to see if the current_user was being set correctly.)
So what it acts like is that dummy statement somehow 'forces' the current_user to be loaded or refreshed or recognized.
Can anyone explain what's going on, and what I should be doing differently so I don't need the dummy statement?
#specs/controllers/brokers_controller_spec.rb
describe BrokersController do
login_admin
def valid_attributes
{:name => "Bill", :email => "rspec_broker#example.com", :company => "Example Inc", :community_id => 1}
end
def valid_session
{}
end
describe "GET index" do
it "assigns all brokers as #brokers" do
dummy=subject.current_user.inspect # ALL SPECS FAIL WITHOUT THIS LINE!
broker = Broker.create! valid_attributes
get :index, {}, valid_session
assigns(:brokers).should eq([broker])
end
end
describe "GET show" do
it "assigns the requested broker as #broker" do
dummy=subject.current_user.inspect # ALL SPECS FAIL WITHOUT THIS LINE!
broker = Broker.create! valid_attributes
get :show, {:id => broker.to_param}, valid_session
assigns(:broker).should eq(broker)
end
end
and per the devise wiki here is how I login a :user or :admin
#spec/support/controller_macros.rb
module ControllerMacros
def login_admin
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:admin]
sign_in Factory.create(:admin) # Using factory girl as an example
end
end
def login_user
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
user = Factory.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
What a struggle! Thank you Robin, I've been googling on this for hours and finally saw your post; now my controller tests are working :)
To add to your answer, I figured out how to get the devise session into the valid_session hash, which allows the controller tests to run properly as generated by rails.
def valid_session
{"warden.user.user.key" => session["warden.user.user.key"]}
end
In your tests, there is the following code:
def valid_session
{}
end
...
get :index, {}, valid_session
Because of this 'session' variable, the "log_in" that you did is essentially not being used during the 'get'.
The way that I solved it was to remove all of the "valid_session" arguments to the get, post, put, delete calls in that controller's spec. The example above becomes:
get :index, {}
I suspect that there's a way to add the devise's session to the "valid_session" hash, but I don't know what it is.
Thanks for this solution.
If you are using a different Devise model, the session id also changes.
For a model Administrator use the following:
def valid_session
{'warden.user.administrator.key' => session['warden.user.administrator.key']}
end