Ruby on Rails - Testing Database - ruby-on-rails

Is there a way to prevent Rails from clearing the test database prior to running the test? I want to use a copy of my production database for testing so I need to prevent rails from clearing out the data every time.
There is quite a bit of data so I would like to avoid using fixtures if possible since I'm assuming it would take a long time to populate the db every time.
Thanks,
Mike

You can avoid it by running the tests manually
ruby -Itest test/unit/some_test.rb
It is the rake task which does the test db recreation (you can run it manually like so)
rake db:test:prepare
But my suggestion is that you're doing it wrong.
The general idea in testing is that you know the state of the database, and therefore know what to expect from a function.
eg.
test "search_by_name" do
expected = User.all.select{|u| u.name =~ /arthur/i}
assert_equal expected, User.search_by_name("Arthur")
end
is a fine test
however, if you don't know the state of the db, how do you know there is an arthur?
The test above would pass in three bad cases;
there are no User records
all Users are called "Arthur"
there are no users called arther.
So its better to create a false reality,
where we know the state of the database.
We want;
at least one user with the name "Arthur"
at least one user with the name "Arthur" as part of another word
at least one non-Arthur user.
a better test, assuming the db is empty, and using factory girl, may be.
test "search_by_name" do
expected = [
Factory.create(:user, :name => "Arthur"),
Factory.create(:user, :name => "MacArthur")
]
not_expected = [Factory.create(:user, :name => "Archer")]
assert_equal expected, User.search_by_name("Arthur")
end

Let me preface this by saying you generally don't want to use production data for testing. That being said...
You could load "seed" data but make sure you don't have any fixtures for it otherwise it will get deleted for every test run.
See this answer for ways to automatically seed data.

Related

How to seed Test db just once in Rails?

I have a large dictionary of 180,000 words that needs to be loaded into the database in order for my app to run and would be useful to test with. Unfortunately, this takes around 30 minutes to seed the database with. Is there anyway to seed the db just once, or to even seed just one table of the db and allowing other tables to be refreshed each run?
EDIT: I ended up using activerecord-import to greatly speed up the seeding process. It now takes 16 seconds and not half an hour. I also noticed that in my /spec/rails_helper.rb file I had the following:
config.before(:suite) do
Rails.application.load_seed # loading seeds
end
I obviously had added it a long time ago and had forgotten about it since this is kind of a template rails_helper file I use. Commenting that out meant I don't run it every time, and if I ever do need to reseed I could, just by uncommenting it.
For some reason I thought incorrectly that rspec just seeded by default, which is not the case.
You can make your seeding more efficient with the new Rails 6 insert_all. This creates multiple records with a single insert and does not instantiate models. OTOH it doesn't do any validation, so be careful.
DictionaryWords.insert_all([
{ word: "foo" },
{ word: "bar" },
])
Alternatively, use activerecord-import.
But it's better not to make 180,000 words at all.
The problem with seeds and fixtures is they're "one size fits all". They must encompass every possible dev and testing situation. They're fragile, one change to the seed might mysteriously break many tests which made assumptions about the fixtures. Seeds will be blown away if you need to reset your databases.
Instead, use a factory and create what you need when you need it. Use a library such as Faker to generate fake but valid data.
For example...
# Assuming you have classes called Dictionary and DictionaryWord
factory :dictionary do
end
factory :dictionary_word do
dictionary
word { Faker::Lorem.unique.word }
end
Then in your tests create words as you need. Here I'm using RSpec.
let(:dictionary) { create(:dictionary) }
let!(:words) { create_list(:dictionary_word, 3, dictionary: dictionary) }
context 'when the word is in the dictionary' do
let(:word) { words.sample }
it 'finds the word' do
expect( dictionary.exists?(word) ).to be_truthy
end
end
context 'when the word is not in the dictionary' do
let(:word) { "septemburary" }
it 'does not find the word' do
expect( dictionary.exists?(word) ).to be_falsey
end
end
And if you need more words for manual testing, open a console and make some.
[1] pry(main)> FactoryBot.create_list(:dictionary_words, 100)
This is not particularly efficient, but you probably don't really need 180,000 words.
I think you can run just once.
first time you should run seed for the test env
RAILS_ENV=test rails db:seed
Now run RSpec

Can/should Ruby on Rails tests persist to the database?

I have a DailyQuestionSet model and a method in it that (is supposed to) go to the database, see if a DailyQuestionSet already exists for that day, if so, return it, or if not, create a new one, save it to the database and return it.
This seems to work when I call it from a Controller but not from a Ruby on Rails automated test of the model. I am not sure if I'm going crazy or missing something.
When
class DailyQuestionSet < ApplicationRecord
def DailyQuestionSet.get_today_dailyquestionset
#dailyquestionset = nil
#questionlist = nil
#dailyquestionset_list = DailyQuestionSet.where('posed_date BETWEEN ? AND ?', DateTime.now.beginning_of_day, DateTime.now.end_of_day).all
if #dailyquestionset_list.empty?
#dailyquestionset = DailyQuestionSet.create!(posed_date: DateTime.now)
#dailyquestionset.save!
else
raise "disaster"
end
return #dailyquestionset
end
end
class DailyQuestionSetTest < ActiveSupport::TestCase
test "make sure today's daily_question_set has been deleted by the setup function" do
# later add function to delete today's daily_question_set so we can create it and then make sure get_today_dailyquestionset has created it once and then we can refer back to the same row
end
test "create daily_question_set and make sure it has questions" do
#dailyquestionset = DailyQuestionSet.get_today_dailyquestionset
....
end
test "create daily_question_set and make sure it has the same questions" do
#dailyquestionset = DailyQuestionSet.get_today_dailyquestionset
....
end
end
What I thought this would do is add a row to the daily_question_sets table in the database every time I run the first test, and then retrieve that row when I run the second test.
But when I look at the test database there's no row in there being created. I think maybe Rails is not committing the transaction to the database?
Or, put more simply, the raise "disaster" exception never gets thrown because get_today_dailyquestionset always returns a new DailyQuestionSet and never gets the one it (should have) created from the database.
I think I might be fundamentally misunderstanding testing in Rails. Should I be messing around with the DB in model tests at all?
Test database is erased on each run and each individual test runs it queries as transactions so you can't share objects from one test to the other.
If you need objects shared between tests use a setup block https://guides.rubyonrails.org/testing.html#putting-it-together or just run both queries one after the other on the same individual test.
It depends on how your tests are set up. Most likely, you have a separate test database for when the test suite is being run. Try running sqlite3 <app-name>_test, from the command line, if you are using the default Rails database solution. If you wanted to view the development database, you would run sqlite3 <app-name>_development.

Why does a sleep between ActiveRecord#first calls change its behaviour?

I've inherited some code written in ruby 1.8.7 with Rails ~> 2.3.15 and it contains a test which looks something like this:
class Wibble < ActiveRecord::Base
# Wibbles have integer primary keys and string names
end
def test
create_two_wibbles
w1 = Wibble.first
sleep 2 # This sleep is necessary to
w2 = Wibble.first
w1.name = "Name for w1"
w2.name = "Name for w2"
w1.save
w3 = Wibble.first
assert(!w3.update_attributes(w2.attributes))
end
That comment next to the sleep line hasn't been cut off, it literally says This sleep is necessary to. Without that sleep, this test fails - removing it changes the behaviour somehow beyond making it run 2 seconds faster. I've been digging through the file's history in our version control system, and the messages were uninformative. I also cannot contact the original author of this test to figure out what they were trying to do.
From my understanding, we're pulling the same record out of the database twice, editing it in two different ways, saving the first, and asserting that we can't then save the second. I suspect this is a test to make sure our database locks the table correctly, but surely if this were to fail our Wibbles would be fine, and ActiveRecord would be at fault. Nobody in the office can figure out why this test may have been necessary, nor what difference the sleep statement might make. Any ideas?
This is likely caused by Active Record caching and memoization stopping the second find from actually going to the DB and get fresh data.
In fact, try printing the object_id of each wibble; they might be the exact same memoized object. If that is the case, then the test kinda makes sense.
You could also do the same test in a controller action and look at the verbose SQL logs from Rails; I'd expect the second find call to tell you it just used cached data and did not actually run any SQL query.

Rails 3.2 has-and-belongs-to-many update association unit test

In my 'notification' model I have a method (notification and contact models both has and belongs to many):
`def self.update_contact_association(contact, notification)
unless contact == nil
notification.contacts.clear
c = Contact.find(contact)
notification.contacts << c
end
end`
that updates the association between a specific notification and contact. It takes a notification object(row) and a list of contact ids. The method works fine, given a single contact id 1 and a notification with the id of 4 updates the table should and will look like this:
notification_id contact_id
4 1
The problem comes in when trying to write a unit test to properly test this method. So far I have:
'test 'update_contact_association' do
notification = Notification.find(4)
contact = Contact.find(1)
Notification.update_contact_association([contact.id], notification)
'end
Running the test method causes no errors, but the test database is not updated to look like the above example, it is just blank. I'm pretty sure I need to use a save or update method to mimic what the controller is doing, but I'm not sure how. I just need the unit test to properly update the table so I can go ahead and write my assertions. Any ideas would be greatly appreciated for I need to test several methods that are very similar/the same as this one.
Tests will generally run any database queries inside of a transaction and rollback that transaction when finished with each test. This will result in an empty database when the tests complete.
This is to ensure a pristine starting point for each test and that tests are not interdependent. A unit test is supposed to be run in isolation, so it should always start from the same database/environment state. It also runs on the smallest amount of code possible, so you don't have to worry about code interaction (yet!).
When you're ready to worry about code interacting, you'll be wanting to build out integration tests. They're longer and will touch on multiple areas of code, running through each different possible combination of inputs so touch as many lines of code as possible (this is what code coverage is all about).
So, the fact that it's blank is normal. You'll want to assert some conditions after you run your update_contact_association method. That will show you that the database is in the expected state and the results of that method are what you expect to happen.

Create item test is failing despite the functionality working in the app

I have a test with failing despite knowing the functionality works in the app. My instinct says that I should try saving the thing that I create but I'm not sure how to do this in the assert_difference block beacause it doesn't look like the new thing is assigned to a variable on which I can .save. Thanks for any advice you can provide.
Test:
test "should create thing" do
assert_difference('thing.count') do
post :create, thing: { thing_type_id: #thing.thing_type_id, name: #thing.name}
end
Output:
1) Failure:
test_should_create_thing(thingsControllerTest) [C:/../thing_controller_test.rb:20]:
"thing.count" didn't change by 1.
<3> expected but was
<2>.
Sounds like you may have some left over state in your database. I see that expected but was <2>, meaning you already have two Things in your DB.
You can try clearing the DB state between tests. Depending on your database check out the database_cleaner gem.
Also, it seems you may have already created the object, by the existence of #thing. If that is the case, this is working as expected.
You can take the controller out of the equation to verify this by just testing a normal Thing::create:
test "creates a new Thing" do
assert_difference('Thing.count') do
Thing.create thing_type_id: #thing.thing_type_id, name: #thing.name
end
end

Resources