Search code examples
ruby-on-railsrubyrspecsearchkick

How can I write RSpec tests that make sure records are being reindexed by Searchkick?


The problem

I'm writing a spec to test whether my Product model gets reindexed when I create an associated Image record.

The docs recommend calling Product.search_index.refresh in tests to make sure that the index is up to date, but that defeats the purpose because I want to make sure that my after_create hooks on Image are causing Product to get reindexed.

Solution 1: Use sleep in my tests

I can call sleep to wait until Searchkick has updated the index, but that slows down my tests and makes them brittle.

product = create(:product)
Product.search_index.refresh

image_name = 'a_lovely_book.png'

search_results = Product.search image_name, fields: [:image_names]

# This passes.
expect(search_results.count).to eq(0)

image = create(:product_image, name: image_name)

# This causes the test to pass because it gives Searchkick time to reindex Product.
sleep 5

# This succeeds if I have the sleep call above.
search_results = Product.search image_name, fields: [:image_names]
expect(search_results.count).to eq(1)

Solution 2: Update the index immediately if Rails.env.test?

I've also considered doing something like this in my Image class so that reindexing happens immediately in tests. But I expect to write a good amount of these kinds of tests, and I don't want to repeat this code over and over again.

class Image
  belongs_to :product
  after_create :reindex_product
  
  def reindex_product
    if Rails.env.test?
      product.search_index.refresh
    else
      product.reindex
    end
  end
end

Solution 3: Use spies or mocks

Not sure how I could do this exactly, but maybe there's a way to use spies or mocks to make sure that the reindex method gets called on Product?


Solution

  • I want to make sure that my after_create hooks on Image are causing Product to get reindexed.

    You're not testing reindexing, just that reindexing is initiated at the appropriate time. So mocks are the way to go. Test the actual reindexing elsewhere, if you feel that's necessary.

    Assuming Image looks something like this:

    class Image < ApplicationRecord
      belongs_to :product
    
      # Note: the docs suggest after_commit so all saves will be reindexed.
      after_commit :reindex_product
    
      def reindex_product
        product.reindex
      end
    end
    

    The test, in RSpec, would look something like...

    describe '.create' do
      it 'reindexes the product' do
        expect(product).to receive(:reindex)
    
        Image.create( product: product, ... )
      end
    end
    
    # This test illustrates why after_create might be insufficient.
    describe '#save' do
      it 'reindexes the product' do
        expect(product).to receive(:reindex)
    
        image = Image.new( product: product, ... )
        image.save!
      end
    end
    

    Or, if you're using asynchronous reindexing, you would check that a reindexing job was queued.