Rails 6 Testing - Initializers and dynamic data with Minitest - ruby-on-rails

I'm currently writing tests for my Rails application using the default Minitest framework and it's working quite fine. The only problem I have that my configuration initializer is run (I guess) every test twice and I can't figure out why this happens.
So, I have an initializer which sets some config parameters from the database for my application:
# config/initializers/load_application_settings.rb
Rails.configuration.after_initialize do
if defined?(::Rails::Server) && !Rails.env.test?
WebMenueNew::Application.config.header_title = "WebMenue | #{Configuration.first.clinicName}"
WebMenueNew::Application.config.clinic_name = Configuration.first.clinicName
WebMenueNew::Application.config.clinic_location = Configuration.first.city
WebMenueNew::Application.config.proxy_server = Configuration.first.request_server
WebMenueNew::Application.config.order_time = Configuration.first.orderTime.strftime('%H:%M')
WebMenueNew::Application.config.repeating_period = Configuration.first.repeatMealTime
WebMenueNew::Application.config.clinic_iban = Configuration.first.iban
WebMenueNew::Application.config.clinic_bic = Configuration.first.bic
WebMenueNew::Application.config.clinic_bank = Configuration.first.bank
end
end
I tried to wrap it witht he if !Rails.env.test? condition, but it doesn't seem to work. In my controller test, I tried to initialize the value in the setup method and destroy all params in the teardown method:
require 'test_helper'
class SessionsControllerTest < ActionDispatch::IntegrationTest
setup do
Rails.configuration.clinic_name = "BDH-Klinik Braunfels"
#user = users(:one)
end
test "should get new" do
get root_path
assert_response :success
assert_select "title", "WebMenue | BDH-Klinik Braunfels"
end
test "should be valid external login" do
post login_path, params: { session: { using_ldap: false, username: #user.email, password: "valid_password" } }
assert_response :redirect
assert_redirected_to #user
end
teardown do
Configuration.destroy_all
end
end
Even if i move it out of the setup method and into the test method for the new action, it always produces two record in the test database and I get the following error:
Minitest::UnexpectedError: ActiveRecord::RecordNotUnique:
PG::UniqueViolation: ERROR: duplicate key value violates unique
constraint "configurations_pkey"
DETAIL: Key (id)=(980190962) already exists.
In my new view for the sessions controller I set the title of the application dynamically, that's why I need the parameter:
<% content_for(:title) { "WebMenue | #{Rails.configuration.clinic_name}" } %>
Is there any workaround so that the initializer is not run on test suites? Or am I missing something? I'm relatively new to testing in Rails so don't be too hard :).

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?

ControllerTest error, Couldn't find Transaction with 'id'=

The Show action for my Controller is not passing the unit test. I'm getting an error in my ControllerTest. Here is the error I get from 'rake test':
> ERROR["test_should_get_show", TransactionsControllerTest, 2015-07-18
> 00:30:18 -0400] test_should_get_show#TransactionsControllerTest
> (1437193818.29s) ActiveRecord::RecordNotFound:
> ActiveRecord::RecordNotFound: Couldn't find Transaction with 'id'=
> app/controllers/transactions_controller.rb:21:in `show'
> test/controllers/transactions_controller_test.rb:6:in `block in <class:TransactionsControllerTest>'
> app/controllers/transactions_controller.rb:21:in `show'
> test/controllers/transactions_controller_test.rb:6:in `block in <class:TransactionsControllerTest>'
>
> 5/5:
> [=======================================================================================================================] 100% Time: 00:00:00, Time: 00:00:00
>
> Finished in 0.24993s 5 tests, 4 assertions, 0 failures, 1 errors, 0
> skips
Despite this error, the view is successfully generates the page in the browser when I use this url:
http://localhost:3000/transactions/1
So, there is an error somewhere. It's not failing.
transactions_controller.rb
class TransactionsController < ApplicationController
def new
#transaction = Transaction.new
end
def create
#transaction = Transaction.new(transaction_params)
if #transaction.save
# Handle a successful save.
else
render 'new'
end
end
def index
#transactions = Transaction.all
end
def show
#transaction = Transaction.find(params[:id]) #this is row 21
end
private
def transaction_params
params.require(:transaction).permit(:company, :month, :account_code, :description, :transaction_amount)
end
end
transaction_controller_test.rb
require 'test_helper'
class TransactionsControllerTest < ActionController::TestCase
test "should get show" do
transaction = Transaction.create
get :show, id: transaction.id #this is row 6
assert_response :success
end
end
The following fixture was automatically generated by rails. I'm new at rails and have to admit that I don't really understand fixtures.
transactions.yml
one:
company: MyString
month: 2015-06-19
account_code: MyString
description: MyString
transaction_amount: 1.5
two:
company: MyString
month: 2015-06-19
account_code: MyString
description: MyString
transaction_amount: 1.5
In my routes file, I'm setting the root to the index action. I plan to access the show action via the url http://localhost:3000/transactions/1 and (as mentioned above) it actually works.
routes.rb
Rails.application.routes.draw do
root 'transactions#index'
resources :transactions
end
transaction.rb
class Transaction < ActiveRecord::Base
validates :month, presence: true
validates :account_code, presence: true
end
transaction_test.rb
require 'test_helper'
class TransactionTest < ActiveSupport::TestCase
def setup
##transaction = transactions(:one)
#transaction = Transaction.new(company: "Inc", month: Date.new(2015,6,15).to_s,
account_code: "80-32100-12-1201-60010",
description: "Salaries & Wages - Histo Gross 1", transaction_amount: 100000)
end
test "should be valid" do
assert #transaction.valid?
end
test "month should be present" do
#transaction.month = " "
assert_not #transaction.valid?
end
test "account_code should be present" do
#transaction.account_code = " "
assert_not #transaction.valid?
end
test "month cannot be a string" do #not sure if this is worth anything at all
#transaction.month = "xxxxxxxxxxxx"
assert_not #transaction.valid?
end
end
The test auto generated by Rails (before I modified it) was very simple:
require 'test_helper'
class TransactionsControllerTest < ActionController::TestCase
test "should get show" do
get :show
assert_response :success
end
end
I modified it to the current version above because (I believe) the test should first create a transaction and then reference id somehow. But I'm not sure I did this correctly.
I've seen questions similar to this on Stackoverflow. For instance, this question was helpful, but didn't quite get me there
Count; find User with id= Minitest
NOTE: Initially, I set up the Controller incorrectly. I set it up as a singular "TransactionController" and changed it to the plural "TransactionsController". So I had to change the name in the unit test also. Perhaps this has had an effect on the app.
So... questions:
why is the unit test failing while the show action is actually
working in the browser?
How might I change the code in the unit test so it recognizes that
I'm using transaction_params?
Do I have to modify the fixtures when I make changes to the
unit_test or the controller?
Ok, so coorasse's suggestion to use create! forced the app to uncover a validation error. The initial error (Couldn't find Transaction with 'id'= ) was replaced with a new error:
ActiveRecord::RecordInvalid: Validation failed: Month can't be blank,
Account code can't be blank
This was confusing because it leads me to think of a model validation error. Why am I getting a model validation error in a ControllerTest? Also, the transaction.rb model clearly shows that that I've added validations to satisfy the presence of Month and AccountCode.
So, I spent some time reading through "A Guide to Testing Rails Applications" on RailsGuides
http://guides.rubyonrails.org/testing.html
All the way down in Section 8, it shows how to include a block of code before each test. Here is the new transaction_controller_test
transaction_controller_test.rb (REVISED)
require 'test_helper'
class TransactionsControllerTest < ActionController::TestCase
# called before every single test
def setup
#transaction = transactions(:one)
end
test "should get show" do
get :show, id: #transaction.id
assert_response :success
end
end
After this the test passed! Unfortunately, the RailsGuides doesn't tell me why I would want to use setup in a controller test. If any smart Rails gurus has some guidance on this, your comments would be appreciated.

Set request variables for functional testing of controllers in rails with minitest

When doing functional tests for controllers in rails how can I provide dynamic application instance variables to my test which live in the request.
I have a #station object that is initialized in a before action in my application controller (available in all other controllers), however the #station object is defined by the domain entry of the user e.g.: blue.mydomain.com. So it could have 3 different flavors, and the controller actions params[:id] are only valid for a certain flavor.
Further if I don't give my #station a flavor for my test environment it will fail utterly:
(Here code from a helper that gets called in a before action in my application_controller.rb)
def init_station
if Rails.env == "test"
#station=Station.new('blue')
else
#station=Station.new(domain_flavor_picker)
end
end
ApplicationController
....
before_action :init_station
.....
end
Thus I can only test for 'blue' or switch the flavor in my before action and then mock for different id!
test:
describe MyController do
before do
#id="10215d8da3f4f278cec747f09985b5528ec9e"
end
it "should get index action" do
p assigns(:station) # is nil
get :artist_biography, id: #id, locale: I18n.available_locales.sample
assert_response :success
assert_not_nil assigns(:meta)
assert_not_nil assigns(:nav)
assert_not_nil assigns(:content)
end
end
As you can see I am also in need of providing a locale variable. I managed to mix up that call with I18n.available_locales.sample
How can I dynamically switch or manipulate my #station instance variable?
My issue was that I needed to provide minitest with an initial host! From #smathy answer I knew that I needed a Mock Request for the Controller!
Turns out that it is quite easy to set it in MiniTest if you know how!
Rails provides an ActionDispatch::TestRequest object which in itself seems to be a Rack::MockRequest object:
DEFAULT_ENV = Rack::MockRequest.env_for('/', 'HTTP_HOST' => 'test.host', 'REMOTE_ADDR' => '0.0.0.0', 'HTTP_USER_AGENT' => 'Rails Testing' )
So all I had to do in my test was:
before do
#request.env['HTTP_HOST'] = %w(blue.mydomain.com red.mydomain.com green.mydomain.com).sample
end
to initialize my #station object with a sample of flavored domains.
assigns :station will only return a value after you do the request, ie. after the get line. Until you've done the request none of your controller code has been run for that test.
You also shouldn't use #vars in rspec, use let instead, and a few other things that I've shown below, many of which I learned from BetterSpecs
The Crux of your Issue
Assuming that domain_flavor_picker is a method in your controller then you should just mock that so you can different tests for the different return values of it. So, this shows the context for one of the return values of domain_flavor_picker, but you'd add other contexts for other values:
describe MyController do
let(:id) { "10215d8da3f4f278cec747f09985b5528ec9e" }
describe "GET /artist_biography" do
context "when domain_flavor is blue" do
before do
allow(controller).to receive(:domain_flavor_picker) { "blue" } # <-- MOCK!
end
context "when valid id" do
before { get :artist_biography, id: id, locale: I18n.available_locales.sample }
subject { response }
it { is_expected.to be_success }
it "should assign :meta" do
expect(assigns :meta).to be_present # better to have an actual matcher here
end
it "should assign :nav" do
expect(assigns :nav).to be_present # ditto
end
it "should assign :content" do
expect(assigns :content).to be_present # ditto
end
end
end
end
end

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.

Change request environment variables in Rails integration testing

I have written a functional test that changes some of the request object's environment variables to simulate a user has logged in.
require 'test_helper'
class BeesControllerTest < ActionController::TestCase
# See that the index page gets called correctly.
def test_get_index
#request.env['HTTPS'] = "on"
#request.env['SERVER_NAME'] = "sandbox.example.com"
#request.env['REMOTE_USER'] = "joeuser" # Authn/Authz done via REMOTE_USER
get :index
assert_response :success
assert_not_nil(assigns(:bees))
assert_select "title", "Bees and Honey"
end
end
The functional test works fine.
Now I want to do something similar as part of integration testing. Here is what I tried:
require 'test_helper'
class CreateBeeTest < ActionController::IntegrationTest
fixtures :bees
def test_create
#request.env['HTTPS'] = "on"
#request.env['SERVER_NAME'] = "sandbox.example.com"
#request.env['REMOTE_USER'] = "joeuser" # Authn/Authz done via REMOTE_USER
https?
get "/"
assert_response :success
[... more ...]
end
end
I get an error complaining that #request is nil. I suspect this has something to do with the session object, but I am not sure how to get it to work.
You can set HTTPS in integration tests with
https!
And set the host name with:
host! "sandbox.example.com"
Which may be equivalent to what you want to do?
This is described in the Rails guides Rails guides
You can change request variables via parameters to post method.
For your case, method test_create will be:
def test_create
https!
get "/", nil, { 'SERVER_NAME'] => "sandbox.example.com", 'REMOTE_USER'] => "joeuser" }
assert_response :success
[... more ...]
end
Same works for setting post request to raw data:
post root_path, nil, { 'RAW_POST_DATA' => 'some string' }

Resources