In my Rails application I'm using PostgreSQL schemas and not every schema contains all tables. (Especially the default path does not) This leads to some problems with fixture loading in Test::Unit.
I'm setting the schema search path in setup, but if I enable fixtures using fixtures they get loaded before my setup modifies the search path and the fixtures cannot be loaded.
class ActiveSupport::TestCase
fixtures :model1, :model2 #Fixtures are loaded before setup below
setup do
# Setup schema search path
end
end
Is it possible to delay loading of the fixtures until after setup or to modify the database connection before the fixtures get loaded? (Note: Not all tests work on the same search path!)
Some background: The existing tests are from an older version without different schemas, so I don't want to rewrite all test just yet using some other means of test data generation, although for future tests I'll definitly switch to factory_girl.
Related
My legacy code is using DatabaseCleaner which wipes out database after each test being done. However it also wipes out loaded fixtures data and causes some tests which are using fixtures to fail, something like:
ActiveRecord::RecordNotFound Exception: Couldn't find Country with 'id'=593363170
To make them work together, how can I rewrite all fixtures back to database during test? (I mean not rake tasks)
Solution:
Set self.use_transactional_fixtures = false
at the beginning of the file before which you want to reload fixtures.
Or use use_transactional_tests in Rails 5.
Do not set it in spec_helper as it applies globally so conflicts with DatabaseCleaner.
My problems:
The customer database is in a machine over a VPN, so my tests are very slow (about 30-40 seconds each)
This database is not entirely for my application and my data are related to the data that was already on the database
Composite primary keys are present on the old database tables and the informix adapter does not support it for fixtures
What I'm using:
Ruby on Rails 4.2.5
Jruby 9.0.1.0
Informix database via JDBC connection
Minitest
Minitest-reportes
What I did so far:
The test time was decreased in 5-10 seconds with Devise.stretches = 1 and Rails.logger.level = 4 configs.
I can't use fixture for the data that have relations with the tables that was in the system and have composite primary keys, so I built my own fixture methods for this tables, removing and creating objects at the first use and caching them in global variables after create to can be used without go to the database on next tests. This data I was inserting almost every test, so with this I could decrease the time a little more.
Apparently rails fixtures was deleting and creating them all on every test, so I tried to disable use of transactional fixtures (self.use_transactional_fixtures = false) but apparently this does not change a thing.
I need to reset "my fixtures" before the test suite and unfortunately rails fixtures are depending on them and when I'm trying to delete my data I'm getting some errors. Tried to drop "my fixtures" before creating rails fixtures with this code:
class ActiveRecord::FixtureSet
class << self
alias :orig_create_fixtures :create_fixtures
end
def self.create_fixtures f_dir, fs_names, *args
reset_cache
#Deleting my fixture data
People.delete_all
orig_create_fixtures f_dir, fs_names, *args
end
end
The problem with the above code is delete_all is being executed on each test.
Besides the errors, I'm getting with all this fixture stuff, I reduced my tests time to about 8 seconds each.
My test helper:
ENV["RAILS_ENV"] = "test"
require File.expand_path('../../config/environment', __FILE__)
require "rails/test_help"
require 'minitest/autorun'
require 'minitest/reporters'
Minitest.backtrace_filter = Minitest::BacktraceFilter.new
# Load support files
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
ActiveSupport::TestCase.class_eval do
extend MiniTest::Spec::DSL
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
fixtures :all
self.use_transactional_fixtures = false
Devise.stretches = 1
Rails.logger.level = 4
reporter_options = {color: true, slow_count: 5}
Minitest::Reporters.use! [Minitest::Reporters::DefaultReporter.new(reporter_options)]
end
Can I do something better? Any clues to solve this problem with removing data? Any way to improve a little more my tests performance?
I've been in Factory land for the past few years and have decided to come back to fixtures but am running into a problem.
In my test_helper.rb I have
class ActiveSupport::TestCase
fixtures :all
end
Then in an individual test case I might be doing something like users(:one) however the data for users(:two) and the data for other tables that I am not calling on seems to be present in the test database.
So, is this the expected behavior? I have a hard to believing it is, seems strange from an isolated testing perspective.
This happens because you load all the fixtures at once from fixtures :all statement. A rule of thumb in tests is to load only the required data for a given test (not all). This also could slow down your test running time.
If you want to load only the selected fixtures you could do
fixtures :<fixture name>
Ex:
fixtures :users
Read more about fixtures.
One more thing, do you have a particular reason to comeback to fixtures. Factories are the way to go; they allow you do organize your test data cleanly.
Read more about factories.
I was wondering how you were testing the search in your application when using ElasticSearch and Tire.
How do you setup a new ElasticSearch test instance? Is there a way to mock it?
Any gems you know of that might help with that?
Some stuff I found helpful:
I found a great article answering pretty much all my questions :)
http://bitsandbit.es/post/11295134047/unit-testing-with-tire-and-elastic-search#disqus_thread
Plus, there is an answer from Karmi, Tire author.
This is useful as well: https://github.com/karmi/tire/wiki/Integration-Testing-Rails-Models-with-Tire
I can't believe I did not find these before asking...
Prefixing your index-names for the current environment
You could set a different index-name for each environment (in your case: the test environment).
For example, you could create an initializer in
config/initializers/tire.rb
with the following line:
Tire::Model::Search.index_prefix "#{Rails.application.class.parent_name.downcase}_#{Rails.env.to_s.downcase}"
A conceivable approach for deleting the indexes
Assuming that you have models named Customer, Order and Product, put the following code somewhere at your test-startup/before-block/each-run-block.
# iterate over the model types
# there are also ways to fetch all model classes of the rails app automaticly, e.g.:
# http://stackoverflow.com/questions/516579/is-there-a-way-to-get-a-collection-of-all-the-models-in-your-rails-app
[Customer, Order, Product].each do |klass|
# make sure that the current model is using tire
if klass.respond_to? :tire
# delete the index for the current model
klass.tire.index.delete
# the mapping definition must get executed again. for that, we reload the model class.
load File.expand_path("../../app/models/#{klass.name.downcase}.rb", __FILE__)
end
end
Alternative
An alternative could be to set up a different ElasticSearch instance for testing on another port, let's say 1234.
In your enviornment/test.rb you could then set
Tire::Configuration.url "http://localhost:1234"
And at a suitable location (e.g. your testing startup) you can then delete all indexes on the ElasticSearch testing-instance with:
Tire::Configuration.client.delete(Tire::Configuration.url)
Maybe you must still make sure that your Tire-Mapping definitions for you model classes are still getting called.
I ran into a quirky bug when deleting my elasticsearch index via tire in my rspec suite. In my Rspec configuration, similar to the Bits and Bytes blog, I have an after_each call which cleans the database and wipes out the index.
I found I needed to call Tire's create_elasticsearch_index method which is responsible for reading the mapping in the ActiveRecord class to set up the appropriate analyzers, etc. The issue I was seeing was I had some :not_analyzed fields in my model which were actually getting analyzed (this broke how I wanted faceting to work).
Everything was fine on dev, but the test suite was failing as facets were being broken down by individual words and not the entire multi word string. It seems that the mapping configuration was not being created appropriately in rspec after the index was deleted. Adding the create_elasticsearch_index call fixed the problem:
config.after(:each) do
DatabaseCleaner.clean
Media.tire.index.delete
Media.tire.create_elasticsearch_index
end
Media is my model class.
I ran into similar issues and here's how I solved it. Bare in mind that my solution builds on top of #spaudanjo solution. Since I'm using spork, I add this inside the spec_helper.rb's Spork.each_run block, but you may add this into any other each/before block.
# Define random prefix to prevent indexes from clashing
Tire::Model::Search.index_prefix "#{Rails.application.class.parent_name.downcase}_#{Rails.env.to_s.downcase}_#{rand(1000000)}"
# In order to know what all of the models are, we need to load all of them
Dir["#{Rails.root}/app/models/**/*.rb"].each do |model|
load model
end
# Refresh Elastic Search indexes
# NOTE: relies on all app/models/**/*.rb to be loaded
models = ActiveRecord::Base.subclasses.collect { |type| type.name }.sort
models.each do |klass|
# make sure that the current model is using tire
if klass.respond_to? :tire
# delete the index for the current model
klass.tire.index.delete
# the mapping definition must get executed again. for that, we reload the model class.
load File.expand_path("../../app/models/#{klass.name.downcase}.rb", __FILE__)
end
end
It basically defines it's own unique prefix for every test case so that there are no in indexes. The other solutions all suffered from a problem where even after deleting the index, Elastic Search wouldn't refresh the indexes (even after running Model.index.refresh) which is why the randomized prefix is there.
It also loads every model and checks if it responds to tire so that we no longer need to maintain a list of all of the models that respond to tire both in spec_helper.rb and in other areas.
As this method doesn't "delete" the indexes after using it, you will have to manually delete it on a regular basis. Though I don't imagine this to be a huge issue, you can delete with the following command:
curl -XDELETE 'http://localhost:9200/YOURRAILSNAMEHERE_test_*/'
To find what YOURRAILSNAMEHERE is, run rails console and run Rails.application.class.parent_name.downcase. The output will be your project's name.
We have a ton of non-user data: 500 product types that haven't changed in 20 years, 200GB of geospatial data that never changes (Queens is always 40.73N/73.82W)... etc. It's the same in Development and Production. It should be the same in Test, but the during testing Rails empties all of the test tables, and it takes a ton of time to re-populate during testing.
What is the Rails way to partition this non-user data so that it doesn't have to be repopulated in Test?
The documentation for this is found in the Fixtures class. (Search for "Transactional fixtures" on that page.)
The example they give starts out like this:
class FooTest < ActiveSupport::TestCase
self.use_transactional_fixtures = true
self.use_instantiated_fixtures = false
...
One of the projects I work on uses a test database with zero fixtures, so we just define this globally in test_helper.rb.
class ActiveSupport::TestCase
self.use_transactional_fixtures = true
self.use_instantiated_fixtures = false
end
jdl shows you the settings to use for enabling and disabling transactional_fixtures, you should be able to set:
# Use self in ActiveSupport::TestCase if you don't have a config block
# in test/test_helper.rb
#
config.use_transactional_fixtures = false
And stop Rails from trying to load fixtures before each run of your tests. The downside is you can't assume all your fixtures are loaded into the DB.
Real Answer
You have too much data in your fixtures. The Railsy thing to do it load only the necessary data in test fixtures; You do not need 200G of geospatial data (what freaking dataset is that anyway? that sounds way too big), and you probably don't need all 500 products.
Tests are for your code, so only include the few geospatial points you need for a test, or only include a few products with unique properties. Keep the DB light enough to be loaded quickly.
Your test fixtures should be separate from you application bootstrapping data, take advantage of that.
Real Real Answer
Don't touch the database in your tests (or, touch it very little). The database is slow and fixtures are difficult or impossible to maintain well. Instead, try using a stubbing framework like Mocha or flexmock. It's faster, and makes your tests read in a clearer manner. Tests are for code, you can stub the database out of the situation and trust that ActiveRecord works (because...it's tested too! :-).
If you do choose to stick with fixtures, I recommend using factory_girl instead of the built in Rails fixtures. You'll have a much better starting point.