RSpecでElasticsearchを使ったテストを書く

井原(@ihara2525)です。

以下のようにElasticsearchを使って検索するアクションがあり、RSpecでこのコントローラスペックを書きたい!という場合にどうしようって話です。

class PostsController < ApplicationController
  def search
    @records = Post.search(params[:query]).page(params[:page]).records
  end
end

Gemfileにelasticsearch-extensionsを追加します。バージョンは適当なもので。 このgemがデフォルトで9250ポートで立ち上がるテスト用のElasticsearchのクラスタを用意してくれるので、9200で立ち上がる開発用のクラスタとかぶりません。

Gemfile

group :test do
  gem 'elasticsearch-extensions', '0.0.15'

で、毎回立ち上げたり落としたりしていると時間がかかって仕方がないので、:elasticsearchなときだけ動かすようにします。

require 'elasticsearch/extensions/test/cluster'

RSpec.configure do |config|
  config.before(:all, :elasticsearch) do
    Elasticsearch::Extensions::Test::Cluster.start(nodes: 1) unless Elasticsearch::Extensions::Test::Cluster.running?
  end

  config.after(:all, :elasticsearch) do
    Elasticsearch::Extensions::Test::Cluster.stop if Elasticsearch::Extensions::Test::Cluster.running?
  end
end

Elasticsearchのクライアントが使うホストやポートは、環境変数なりで変えられるようにしておいた方が良いですね。

config/initializers/elasticsearch.rb

Elasticsearch::Model.client = Elasticsearch::Client.new(host: ENV['ELASTICSEARCH_HOST'], logs: Rails.env.development?, trace: Rails.env.development?)

僕はdotenvを使って、.env.testでテスト用の環境変数を設定するようにしています。 テスト用なのでlocalhostの9250番を見るようにしておきます。

.env.test

ELASTICSEARCH_HOST='localhost:9250'

コントローラのテストはこんな感じ。

spec/controllers/posts_controller_spec.rb

RSpec.describe PostsController, :elasticsearch do
  let(:page) { FactoryGirl.create(:page, content: 'ページ') }

  before do
    Post.__elasticsearch__.create_index! force: true
    Post.__elasticsearch__.refresh_index!
    page
    Post.import
    # Wait test cluster to index the created objects
    sleep 1
  end

  after { Post.__elasticsearch__.client.indices.delete index: Post.index_name }

  context 'when the result exists' do
    it 'assigns the result to @results' do
      get :search, query: 'ページ'
      expect(assigns(:records)).to contain_exactly(page)
    end
  end
end

最初に:elasticsearchを指定しているので、このテスト全体が動く前にテスト用のElasticsearchのクラスタが立ち上がり、終わる前にクラスタが終了します。

あとは、各テストの前でインデックスがつくられるようにbeforeにデータを突っ込む処理を書き、afterでつくられたインデックスを削除しています。つくったデータがインデックスに反映されるのを待つために1秒スリープしているのがダサいですが、仕方ないですかね。。

というわけで、RSpecでElasticsearchを使ったテストをするサンプルをあまり見つけられなかったので書いておきました!