環境
railsとelasticsearchをそれぞれ別コンテナで動かしている
services:
elasticsearch:
image: elastcserachのdockerイメージ
(以下省略)
rails:
image: railsのdockerイメージ
(以下省略)
テスト用にGemを追加
テスト用のクラスタを別ポート(デフォルトは9250)で立ち上がることができる
https://github.com/elastic/elasticsearch-ruby/tree/master/elasticsearch-extensions
テスト用のクラスタを立ち上げる
elasticsearchを使うときだけ立ち上げ、終了したら止める
require 'elasticsearch/extensions/test/cluster'
before(:context) do
Elasticsearch::Extensions::Test::Cluster.start(nodes: 1) unless Elasticsearch::Extensions::Test::Cluster.running?
end
after(:context) do
Elasticsearch::Extensions::Test::Cluster.stop if Elasticsearch::Extensions::Test::Cluster.running?
end
実行したらエラーになる、、
Cannot find Elasticsearch launch script from [elasticsearch] -- did you pass a correct path?
elasticsearchとrailsのコンテナが違うので、railsのコンテナでelasticsearchを起動するには、railsのコンテナでelasticsearchを使えるようにしないといけないみたい。。。。
方向性を変えて、DB運用と同様に、テスト用のindexを用意する(先ほど追加したgemも削除)
ということで、alias_nameを環境ごとに分ける
def alias_name
"players_#{Rails.env}"
end
今回扱うデータ
Playerという選手のidと名前をカラムに持つテーブルのデータを扱う
id | name |
---|---|
1 | xxxx |
2 | xxxx |
下記のようにelasticsearchへ入れるデータを定義する
require 'elasticsearch/model'
module SearchablePlayer
extend ActiveSupport::Concern
included do
include Elasticsearch::Model
include Indexing
index_name "players_#{Rails.env}_#{Time.now.strftime('%Y%m%d%H%M%S')}"
settings do
mappings dynamic: false do
indexes :id, type: 'integer'
indexes :name, type: 'string'
end
end
end
module Indexing
def as_indexed_json(options={})
{
id: id,
name: name,
}
end
end
module ClassMethods
def alias_name
"players_#{Rails.env}"
end
end
end
テスト用のクラスタにデータを入れる
rspec内にテーブルの中身をelasticsearchへ反映するためのメソッドを定義する
(elasticsearchへの反映には時間がかかるので、1秒sleepを入れる)
このとき、index名は「players_test」となるので、開発環境のindexには影響はない
def elasticsearch_update
index_name = Player.index_name
alias_name = Player.alias_name
# Playerのindexを追加
Player.__elasticsearch__.import(
force: true,
index: index_name
)
# Playerのindexを追加
actions = [{
add: {
index: index_name,
alias: alias_name
}
}]
Player.__elasticsearch__.client.indices.update_aliases(
body: { actions: actions }
)
# インデックスに反映するために1秒待つ
sleep 1
end
リクエストスペックを書く
今回は選手の一覧を返却するAPIのテストを書く
beforeでテーブルにレコードを作成してから、先ほど定義したメソッドを呼ぶ
describe '選手の一覧を返却するAPI'
before do
# データを作成する
FactoryBot.create(players, name: 'test')
# 作成したデータをelasticsearchへ反映させる
elasticsearch_update
end
it do
get 選手一覧のAPIパス
expect(レスポンスの選手名).to eq(test)
end
end
テストが終わったらindexを削除する
このままだとテストを回すごとにindexが追加されていってしまうので、
テストが終わったらindexを削除する
また、elasticsearchへデータを投入しないパターンもあるので、
その場合にNotFoundの例外が起きるので、rescueする(もっといい方法があるかも、、)
after(:example) do
player_client = Player.__elasticsearch__.client
alias_name = Player.alias_name
begin
delete_indices = player_client.indices.get_alias(name: alias_name).keys
player_client.indices.delete(index: delete_indices)
rescue Elasticsearch::Transport::Transport::Errors::NotFound
end
end
まとめ
gem(Elasticsearch::Extensions)が使えなかったが
DB運用のように、テスト用のindexを作ることで十分にテストを書くことができた。(特に弊害もなさそう?)
参考
https://techlife.cookpad.com/entry/2015/09/25/170000
https://blog.bitjourney.com/entry/2015/05/22/162250