Testing Solr and Sunspot (locally and on CircleCI)

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
↩