Rails Test::Unit test of model validation surprisingly slow – I18n? - ruby-on-rails

I have a simple test in my Rails app which is consistently taking much longer than other apparently similar tests.
It takes 1 to 2 seconds of real time to run test_invalid_without_name as below:
VALID_PARAMS = { name: 'Client record' }
def test_invalid_without_name
c = Client.new(VALID_PARAMS)
c.name = nil
refute c.valid?, 'should not be valid without a name'
end
The next test, test_valid_with_all_params takes less than 1/100th of a second:
def test_valid_with_all_params
c = Client.new(VALID_PARAMS)
assert c.valid?, 'should be valid with appropriate parameters'
end
The Client model is totally straightforward at this stage:
class Client < ActiveRecord::Base
belongs_to :entity, polymorphic: true
validates :name, presence: true
end
Can anyone either spot what’s wrong here, or give me an idea where I should look next to try to figure this out?
Update 1
I have used ruby-prof to profile the code, and it seems much of the time is being spent in the Psych gem. I believe this is used in ActiveRecord::..#serialize, which I am using in another model as follows:
class Opportunity < ActiveRecord::Base
belongs_to :client
serialize :details, Hash
end
However, removing the call to serialize here doesn’t make any difference to the Client tests (and I can’t see why it would, the two classes are associated, but no Opportunities are instantiated in the Client test).
Update 2
Turns out Psych is being called because I18n is using it. My assumption at this point is that the failed validation causes I18n to go to the locale files to pull out an error message. I will look into stubbing out I18n for testing...

It turns out a suitable approach to solving this was to use ruby-prof as follows.
Add to Gemfile (in the development group):
gem 'ruby-prof'
Wrap the test code in calls to the profiler:
def test_invalid_without_name
RubyProf.start
cr = Client.new(VALID_PARAMS)
cr.name = nil
refute cr.valid?, 'should not be valid without a name'
result = RubyProf.stop
printer = RubyProf::CallStackPrinter.new(result)
printer.print(File.open(File.join(Rails.root, 'profile_invalid_without_name.html'), 'w'))
end
This will create an interactive HTML document at the root of your Rails app, which shows the breakdown of where the time is spent.
In this case, it was 80%+ in I18n looking for translations. I added the following line to config/environments/test.rb to stub it out in testing:
I18n.backend = I18n::Backend::KeyValue.new({})
I will probably make the following additional improvements in future:
Be more specific with where I stub out I18n, so that full-stack acceptance tests can use the real thing.
Write a more convenient profiling approach. It looks like ruby-prof offers various other mechanisms for activating it, this was just the simplest for initial use.

It's not possible to answer the question without more information.
Does this happen when you run the each of tests individually?
Do you have anything in the setup/teardown or your test_helper.rb?
Does it happen if you pass a hash with name => nil instead of setting it?
Did you redefine the name setter?
Do you have any callbacks on your model?

Related

ruby require relative quesiton

Hello I am trying to learn how to make a ruby on rails app. I am stuck on the require_relative syntax. Currently I have a game_runner.rb file, that is not in the app directory, that is supposed to get a method (who_is_hider) from role.rb that is in models. Am I typing it correctly? When I run the current code I get an "uninitialized constant ApplicationRecord" error. This makes no sense since I have a application_record.rb file that has (self.abstract_class = true) inside. Thank you for your time.
game_runner.rb
require_relative './app/models/Role'
who_is_hider
role.rb
class Role < ApplicationRecord
belongs_to :round
belongs_to :player
enum label: {seeker: 1, hider: 2, decoy: 3}
validates :player_id, uniqueness: {scope: :round_id}
def who_is_hider
p "made it here"
end
end
application_record.rb
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end
I am stuck on the require_relative syntax. […] Am I typing it correctly? When I run the current code I get an "uninitialized constant ApplicationRecord" error.
Yes, your syntax is correct. If your syntax were incorrect, your code wouldn't even run in the first place, and you would get a SyntaxError. The fact that your code does run and produce a NameError exception shows without a doubt that your syntax is correct.
As an easy way to check whether your syntax is correct, you can
visually inspect the syntax coloring in your editor,
visually inspect the syntax coloring in the Stack Overflow question, if you post one,
run ruby -c to check the syntax
The last one is the preferred option if you have it available, of course. But sometimes, you are writing code in an environment where you don't have a Ruby execution engine available, and in that case, you can at least use your editor or even the (admittedly not very good and easily confused) highlighting that Stack Overflow uses. While especially the latter one is not very good, it still does tend to highlight easy mistakes caused by typos, e.g. unterminated string literals.
Hmmm, you are using a single ruby file and expecting to load all rails environment? your Role model knows about ApplicationRecord because Rails loads a LOT of things on the background. If Rails is not in the middle then you have to explicitlly tell what thinks to load. You can require ApplicationRecord on the game_runner file or inside role.rb, but then you'll have to require ActiveRecord too.
I don't think it's a good idea to do what you are trying to do. If you wan't to run some script that depends on your environment, I'd recommend you to use a rake task, it will handle the loading of the environment. If you still want to make your game_runner.rb run as is, then you'll have to import everything the interpreter complains about until it works.

Rails 3.2.16 has_many collection build failure

Have a legacy application in Rails 3.2.16 which evaluating for an upgrade and have found an issue when running tests related to an ActiveRecord association between two models. The problem is that the when attempting to construct the associated collection it fails.
I've tested the same scenario is a simple test app using the Ruby on Rails Getting Started Guide which works (details below). The one difference between my test app and the legacy app is that the legacy app models have a number of validations on them.
The models are as follows (extra details removed for ease of reading):
class Schedule < ActiveRecord::Base
has_many :bookings
...
end
class Booking < ActiveRecord::Base
belongs_to :schedule
validate :ensure_sufficient_capacity
...
# validation custom method
def ensure_sufficient_capacity
errors.add(:base, "Not enough capacity") unless capacity_available_for_booking?
end
end
The bookings_controller for the new action has the following sequence:
class BookingController < ApplicationController
def new
#find the named schedule
#schedule = Schedule.find_by_name("myschedule")
#build the booking
#booking = #schedule.bookings.build(:name => "mybooking",...)
#booking.save #fails to save
end
end
Some observations:
The booking model has a validation on it which does a check onto a referenced schedule (the other side of the relation. Can failure on the validation cause a break in the association build ?
Creating a test app as per the Rails Guides Post-Comments getting started example works with a sample of the output below shows that the has_many build operation works.
local> p = Post.new(:title => "foo")
local> #
local> p.save
local> INSERT into "posts" ("title") VALUES ($1) RETURNING "id" [["title", "foo"]]
local> c = p.comments.build(:commenter => "bar")
local> #
local> c.save
local> INSERT INTO "comments" .......
Ok - solved the problem. It seems that it wasn't verifiable based on the code base that I had or the sequence.
The findings were as follows
The test code validated that with a has_many relationship the collections.build for the associated object works and on the save adds in the id for the related object.
Adding validations to the object on the has_many which fail will stop the database transaction on save causing a rollback. This wasn't the problem as the validations were working ok and not returning false
Testing the code on the production server (Heroku) worked ok, but the Git copy I had was wrong and so on checkout of the Heroku code, via heroke git:clone -a appname I setup and tested this against my local database and the whole thing worked.
All in all - it was my fault in not seeing that I had a different version of the codebase. I had assumed that the legacy app provided to me on GitHub was the production version. Unfortunately, as a result of some time based quick fixes, the previous owners had cloned, migrated and deployed to Heroku without an update on Github or connecting the Heroku deployment to Github. This resulted in a disconnect to the source of truth and there was no documentation to point me in the right direction, requiring a lot of reverse engineering.
Anyhow a good reinforcement of the lesson - when it doesn't all add up it's often the simplest of errors causing the problem! Or even it didn't pass the sniff test.
Thanks to all of the suggestion in the comments from Ruby Racer, Pacuna,Fabrizio Bertoglio and I hope that I can reciprocate in the future. I know that I learned some more tricks, especially about checking on the errors.

Rails-How to use MiniTest's Teardown Method

I have been looking around the internet for a long, frustrating, while, and I'm still quite confused as to what the purpose of the teardown() method is in MiniTest and how it should be used.
The basic gist I have is that it is 1-run after each test, and 2-undoes things that were done during the test in order to allow future tests to run in a clean environment.
However, I am unclear on the nature of things it needs to undo: Does it need to empty the DB? Reset class variables? etc.
I am also unclear on whether the method is supposed to be explicitly filled out or not. I have found many examples where teardown() is completely left out of the example.
(My best guess is that there is a super-method teardown that runs automatically and takes care of certain things. That would explain why it is often left out, and would also explain why some things are reset in a given teardown() method and some aren't. But I still don't know which things are and which aren't.)
In short:
Does teardown need to be explicitly created? In what circumstances would it need to be overwritten and in which wouldn't it be?
The simplest answer is that you use #teardown in every test but you don't need to worry about it. Similar to the Rails lifecycle, there is a Minitest lifecycle. There are hooks to inject logic and behavior to be used by your tests. The main one in Rails tests is the database transaction. Each test that uses ActiveSupport::TestCase runs in a database transaction. The flow is like this:
Create the database transaction (Minitest::Test#setup)
Run your test method (MyTest#test_something)
Rollback the database transaction (Minitest::Test#teardown)
It is somewhat common for folks to use #setup to create objects for use in tests. After the test method completes the test object is garbage collected, so most folks don't use #teardown to clean up after the test. Because of this #teardown is typically a more advanced feature that you don't normally use when writing tests. I see it used much more often in testing libraries that enhance Minitest.
But there are times I do use #teardown in my tests. Here is an example of when I might use it.
require "minitest/autorun"
class Foo
def initialize namer
#namer = namer
end
def name
#namer.name
end
end
class FooTest < Minitest::Test
def setup
#namer_mock = Minitest::Mock.new
#namer_mock.expect :name, "foo"
#foo = Foo.new #namer_mock
end
def test_name
assert_equal "foo", #foo.name
end
def teardown
#namer_mock.verify
end
end

How can I prevent ActiveRecord from writing to the DB?

I have a somewhat special use case, where I'd like to create a method that accepts a block, such that anything that happens inside that block is not written to the DB.
The obvious answer is to use transactions like so:
def no_db
ActiveRecord::Base.transaction do
yield
raise ActiveRecord::Rollback
end
end
But the trouble is that if my no_db method is used inside of another transaction block, then I'll ned up in the case of nested transactions. The drawback here is that nested transactions are only supported by MySQL, and I need support for PG, but more importantly SQLite (for tests). (I understand that PG is supported via savepoints, how reliable is that? performance hit?).
The other problem with this type of approach is that it seems really inefficient, writing things to a DB, and then rolling them back. It would be better if I could do something like this:
def no_db_2
# ActiveRecord::Base.turn_off_database
yield
# ActiveRecord::Base.turn_on_database
end
Is there such a method? Or a similar approach to what I'm looking for? I think it needs to be fairly low level..
(Rails version is 3.0.5, but I would be happy if there were an elegant solution for Rails 3.1)
This might be one way to do it:
class Book < ActiveRecord::Base
# the usual stuff
end
# Seems like a hack but you'll get the
# transaction behavior this way...
class ReadOnly < ActiveRecord::Base
establish_connection "#{Rails.env}_readonly"
end
I would think that this...
ReadOnly.transaction do
Book.delete_all
end
...should fail.
Finally, add another connection to config/database.yml
development:
username: fullaccess
development_readonly:
username: readonly
One downside is the lack of support for a read-only mode in the sqlite3-ruby driver. You'll notice that the mode parameter doesn't do anything yet according to the documentation. http://sqlite-ruby.rubyforge.org/classes/SQLite/Database.html#M000071

factory_girl + rspec doesn't seem to roll back changes after each example

Similar to the problem described here:
http://rpheath.com/posts/411-how-to-use-factory-girl-with-rspec
in Short (shorten'd code):
spec_helper:
config.use_transactional_fixtures = true
config.use_instantiated_fixtures = false
factories.rb:
Factory.define :state do
f.name "NY"
end
in my spec
before(:each) do
#static_model = Factory(:state) # with validate uniqueness of state name
end
error:
duplicate entry name "NY" etc.
Question:
Shouldn't rspec clear database before each spec example and hence not throwing duplicate entry errors?
Things i think off:
do you use rake spec to run your testsuite: that builds up the database from scratch (to make sure nothing is sticking)
do you use, anywhere, a before (:all) ? Because whatever you create inside a before :all should be deleted again in a after :all or it keeps on existing.
Question: Shouldn't rspec clear database before each spec example and hence not throwing duplicate entry errors?
RSpec with DatabaseCleaner or RSpec Rails with use_transactional_fixtures will clear the DB as long as your created the data in the example itself. before :all do ... end is considered outside of the example, because the data remains untouched across multiple examples. Whatever you create in before :all you have to delete in after :all.
In order to delete whatever you create automatically use before :each do ... end. Be aware the same data will be created and removed 10 times if you have 10 examples. The difference between before :all and before :each is better explained here: rails rspec before all vs before each
Some more possible causes:
There's still a states.yml fixture sitting around
Someone played around on script/console test and forgot to clean up afterwards.
You might also find it's because you haven't wrapped the statement in:
describe "what it should do" do
#static_model = Factory(:state) # with validate uniqueness of state name
end
I discovered that was the change that solved this problem:
Why isn't factory_girl operating transactionally for me? - rows remain in database after tests
I have had similar questions about what sort of starting state one can expect when using FG and RSpec.
While I too wait for clarity, Database Cleaner could be a good fix: http://rubydoc.info/gems/database_cleaner/0.6.7/frames
hth -
Perry
When you use Factory(:state) wich is a shortcut to Factory.create(:state), factory_girl returns you a saved object.
Use Factory.build(:state) instead.
Dude maybe your yaml fixtures from regular unit tests get mixed into your rspec?

Resources