DatabaseCleaner / database_cleaner-active_record

Strategies for cleaning databases using ActiveRecord. Can be used to ensure a clean state for testing.
MIT License
56 stars 58 forks source link

Wrapping ActiveRecord test causes it to fail (no records found) #13

Open hansondr opened 7 years ago

hansondr commented 7 years ago

I created a script to demonstrate this behavior:

#!/usr/bin/env ruby

begin
  require "bundler/inline"
rescue LoadError => e
  $stderr.puts "Bundler version 1.10 or later is required.  Please update your bundler"
  raise e
end

gemfile(true) do
  source "https://rubygems.org"
  gem "activerecord", "4.2.7.1"
  gem "mysql2", "0.4.5"
  gem "database_cleaner", "1.5.3"
  gem "rspec", "3.5.0"
end

require "active_record"
require "rspec"
require "rspec/autorun"
require "database_cleaner"

ActiveRecord::Base.establish_connection(
  adapter: "mysql2",
  username: "root",
  password: "password",
  host: "localhost",
  database: "ft_test"
)
ActiveRecord::Base.connection.recreate_database "ft_test"

ActiveRecord::Schema.define do
  create_table :widgets, force: true do |t|
    t.string "description", limit: 255
  end

  add_index "widgets", ["description"], name: "widgets_description_fts", type: :fulltext
end

class Widget < ActiveRecord::Base
  def self.text_search(query)
    where("MATCH(description) AGAINST(:query)", query: query)
  end

  def self.like_search(query)
    where("description like ?", "%#{query}%")
  end
end

RSpec.configure do |config|
  config.default_formatter = "doc"
  config.before(:suite) do
    DatabaseCleaner.clean_with(:truncation)
  end
  config.before(:each) do
    DatabaseCleaner.strategy = :transaction
  end
end

RSpec.describe Widget, type: :model do
  describe ".text_search" do
    after(:all) { Widget.delete_all }
    it "fails when cleaning with start/clean" do
      DatabaseCleaner.start

      text_search_test

      DatabaseCleaner.clean
    end

    it "fails when cleaning with block" do
      DatabaseCleaner.cleaning do
        text_search_test
      end
    end

    it "succeeds with manual cleaning (fails at random, unless it is first or the only test run)" do
      Widget.delete_all

      text_search_test

      Widget.delete_all
    end
  end

  describe ".like_search" do
    it "succeeds when cleaning with start/clean" do
      DatabaseCleaner.start

      like_search_test

      DatabaseCleaner.clean
    end

    it "succeeds when cleaning with block" do
      DatabaseCleaner.cleaning do
        like_search_test
      end
    end

    it "succeeds with manual cleaning" do
      Widget.delete_all

      like_search_test

      Widget.delete_all
    end
  end

  private

  def create_widgets
    Widget.create()
    match = Widget.create(description: "The quick brown fox jumps over the lazy dog")
    Widget.create()

    match
  end

  def text_search_test
    match = create_widgets
    expect(Widget.text_search("fox")).to contain_exactly match
  end

  def like_search_test
    match = create_widgets
    expect(Widget.like_search("fox")).to contain_exactly match
  end
end
hansondr commented 7 years ago

Expected output:

Fetching gem metadata from https://rubygems.org/..........
Fetching version metadata from https://rubygems.org/.
Resolving dependencies...
Using i18n 0.7.0
Using json 1.8.3
Using minitest 5.10.1
Using thread_safe 0.3.5
Using builder 3.2.2
Using arel 6.0.4
Using database_cleaner 1.5.3
Using diff-lcs 1.2.5
Using mysql2 0.4.5
Using rspec-support 3.5.0
Using bundler 1.13.7
Using tzinfo 1.2.2
Using rspec-core 3.5.4
Using rspec-expectations 3.5.0
Using rspec-mocks 3.5.0
Using activesupport 4.2.7.1
Using rspec 3.5.0
Using activemodel 4.2.7.1
Using activerecord 4.2.7.1
-- create_table(:widgets, {:force=>true})
   -> 0.0701s
-- add_index("widgets", ["description"], {:name=>"widgets_description_fts", :type=>:fulltext})
   -> 0.4454s

Randomized with seed 46507

Widget
  .text_search
    succeeds with manual cleaning (fails at random, unless it is first or the only test run)
    fails when cleaning with start/clean (FAILED - 1)
    fails when cleaning with block (FAILED - 2)
  .like_search
    succeeds when cleaning with block
    succeeds when cleaning with start/clean
    succeeds with manual cleaning

Failures:

  1) Widget.text_search fails when cleaning with start/clean
     Failure/Error: expect(Widget.text_search("fox")).to contain_exactly match

       expected collection contained:  [#<Widget id: 5, description: "The quick brown fox jumps over the lazy dog">]
       actual collection contained:    []
       the missing elements were:      [#<Widget id: 5, description: "The quick brown fox jumps over the lazy dog">]
     # ./mysql2_full_text_issue.rb:122:in `text_search_test'
     # ./mysql2_full_text_issue.rb:66:in `block (3 levels) in <main>'

  2) Widget.text_search fails when cleaning with block
     Failure/Error: expect(Widget.text_search("fox")).to contain_exactly match

       expected collection contained:  [#<Widget id: 8, description: "The quick brown fox jumps over the lazy dog">]
       actual collection contained:    []
       the missing elements were:      [#<Widget id: 8, description: "The quick brown fox jumps over the lazy dog">]
     # ./mysql2_full_text_issue.rb:122:in `text_search_test'
     # ./mysql2_full_text_issue.rb:73:in `block (4 levels) in <main>'
     # ./mysql2_full_text_issue.rb:72:in `block (3 levels) in <main>'

Finished in 0.68526 seconds (files took 1.07 seconds to load)
6 examples, 2 failures

Failed examples:

rspec ./mysql2_full_text_issue.rb:63 # Widget.text_search fails when cleaning with start/clean
rspec ./mysql2_full_text_issue.rb:71 # Widget.text_search fails when cleaning with block

Randomized with seed 46507
hansondr commented 7 years ago

I've temporarily sidestepped this issue but selectively disabling database cleaner on specs in which this error is present:

#  spec/support/database_cleaner.rb
RSpec.configure do |config|
  # ...
  config.before(:each) do
    DatabaseCleaner.start unless manually_cleaned?
  end

  config.append_after(:each) do
    DatabaseCleaner.clean unless manually_cleaned?
  end

  def manually_cleaned?
    self.class.metadata[:manually_cleaned]
  end
end

Then to skip automatic cleaning in a spec:

RSpec.describe Widget, manually_cleaned: true do
  # ...
end
dnstufff commented 2 years ago

Try this solution. I think it might be what you are searching for: https://stackoverflow.com/a/69724116/537648