Testing Solr and Sunspot (locally and on CircleCI)

David Eisinger, Development Director

Article Categories: #Code, #Back-end Engineering

Posted on

All the configuration necessary to test your Solr- and Sunspot-powered search.

I don't usually write complex search systems, but when I do, I reach for Solr and the awesome Sunspot gem. I pulled them into a recent client project, and while Sunspot makes it a breeze to define your search indicies and queries, its testing philosophy can best be described as "figure it out yourself, smartypants."

I found a seven-year old code snippet that got me most of the way, but needed to make some updates to make it compatible with modern RSpec and account for a delay on Circle between Solr starting and being available to index documents. Here's the resulting config, which should live in spec/support/sunspot.rb:

require 'sunspot/rails/spec_helper'
require 'net/http'

try_server = proc do |uri|
  begin
    response = Net::HTTP.get_response uri
    response.code != "503"
  rescue Errno::ECONNREFUSED
  end
end

start_server = proc do |timeout|
  server = Sunspot::Rails::Server.new
  uri = URI.parse("http://0.0.0.0:#{server.port}/solr/default/update?wt=json")

  try_server[uri] or begin
    server.start
    at_exit { server.stop }

    timeout.times.any? do
      sleep 1
      try_server[uri]
    end
  end
end

original_session = nil # always nil between specs
sunspot_server   = nil # one server shared by all specs

if defined? Spork
  Spork.prefork do
    sunspot_server = start_server[60] if Spork.using_spork?
  end
end

RSpec.configure do |config|
  config.before(:each) do |example|
    if example.metadata[:solr]
      sunspot_server ||= start_server[60] || raise("SOLR connection timeout")
    else
      original_session = Sunspot.session
      Sunspot.session = Sunspot::Rails::StubSessionProxy.new(original_session)
    end
  end

  config.after(:each) do |example|
    if example.metadata[:solr]
      Sunspot.remove_all!
    else
      Sunspot.session = original_session
    end

    original_session = nil
  end
end

(Fork me at https://gist.github.com/dce/3a9b5d8623326214f2e510839e2cac26.)

With this code in place, pass solr: true as RSpec metadata1 to your describe, context, and it blocks to test against a live Solr instance, and against a stub instance otherwise.

A couple other Sunspot-related things #

While I've got you here, thinking about search, here are a few other neat tricks to make working with Sunspot and Solr easier.

Use Foreman to start all the things #

Install the Foreman gem and create a Procfile like so:

rails: bundle exec rails server -p 3000
webpack: bin/webpack-dev-server
solr: bundle exec rake sunspot:solr:run

Then you can boot up all your processes with a simple foreman start.

Configure Sunspot to use the same Solr instance in dev and test #

By default, Sunspot wants to run two different Solr processes, listening on two different ports, for the development and test environments. You only need one instance of Solr running — it'll handle setting up a "core" for each environment. Just set the port to the same number in config/sunspot.yml to avoid starting up and shutting down Solr every time you run your test suite.

Sunspot doesn't reindex automatically in test mode #

Just a little gotcha: typically, Sunspot updates the index after every update to an indexed model, but not so in test mode. You'll need to run some combo of Sunspot.commit and [ModelName].reindex after making changes that you want to test against.


That's all I've got. Have a #blessed Tuesday and a happy holiday season.

1. e.g. describe "viewing the list of speakers", solr: true do

David Eisinger

David is Viget's managing development director. From our Durham, NC, office, he builds high-quality, forward-thinking software for PUMA, the World Wildlife Fund, NFLPA, and many others.

More articles by David

Related Articles