Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Database empty in new Thread / Process testing with Rspec + Factory Girl

Following this blog post, http://blog.arkency.com/2015/09/testing-race-conditions/

I am trying to test concurrency. But in my specs, I am not able to find the record when I spin up a new thread or fork a process.

describe 'test concurrency' do
  let(:order_1) { create(:order) }
  let(:order_2) { create(:order) }
  let(:order_3) { create(:order) }
  let(:order_4) { create(:order) }
  let(:product) { create(:product) }

  it 'test concurrency' do
    wait_for_all_threads = true
    product.update(quantity_available: 4, quantity_sold: 0, quantity_in_carts: 0)
    orders = [order_1, order_2, order_3, order_4]

    # Objects are persisted in here
    # because order_n.persisted => true
    # product.persisted => true
    # product.reload works without issue (and this is essentially what causes the RecordNotFound error in Order#add_item)

    threads = orders.map do |order|
      Thread.new do

        # Objects cannot be found in the database

        true while wait_for_all_threads

        item = order.add_item(product, 1)
        order_items << item
      end
    end

    wait_for_all_threads = false

    threads.each(&:join)

    # ...
    # here comes all the expects ...
    # not gonna post it because it's not what matters in this question
  end
end

After research, I've set these:

DatabaseCleaner.strategy = :truncation
config.use_transactional_fixtures = false

But no luck.

So the issue is, within the new thread, none of the created objects can be found in the database.

Some clues as I am digging:

  • The database is empty the entire time when I connect directly via a sql client. (When I add a break point in the test, and verify the object is persisted?)
  • Does factory girl / rspec actually commit the data to the database? If not, then a new thread would not be able to read it because it's still in the bin log or memory? (This prompts to me because I remember after_commit won't run in rspec)
like image 545
Edmund Lee Avatar asked Mar 02 '17 00:03

Edmund Lee


People also ask

Does RSpec clean database?

I use the database_cleaner gem to scrub my test database before each test runs, ensuring a clean slate and stable baseline every time. By default, RSpec will actually do this for you, running every test with a database transaction and then rolling back that transaction after it finishes.

What is FactoryBot in RSpec?

Factory Bot is a helper for writing factories for Ruby tests. It was previously known as Factory Girl. For older versions, use FactoryGirl instead of FactoryBot . Factory Bot documentation (rubydoc.info) Getting started (github.com)

What RSpec method is used to create an example?

The describe Keyword The word describe is an RSpec keyword. It is used to define an “Example Group”. You can think of an “Example Group” as a collection of tests. The describe keyword can take a class name and/or string argument.

What is RSpec testing?

RSpec is a testing tool for Ruby, created for behavior-driven development (BDD). It is the most frequently used testing library for Ruby in production applications. Even though it has a very rich and powerful DSL (domain-specific language), at its core it is a simple tool which you can start using rather quickly.


1 Answers

Using Rails 5 this worked for me. Place within the describe:

self.use_transactional_tests = false

"use_transactional_fixtures" has been deprecated.

I also had to bypass DatabaseCleaner via the following:

it 'tests concurrency', bypass_cleaner: true do

end

Then in rails_helper (or where you have DatabaseCleaner settings)

config.around(:each) do |example|
  if example.metadata[:bypass_cleaner]
    example.run
  else
    # database cleaner code
  end
end

I hope this helps you or somebody else out :)

like image 111
Dan Avatar answered Oct 16 '22 17:10

Dan