1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Elasticsearchを含むRspecのリクエストスペックを書いてみた

Last updated at Posted at 2020-07-24

環境

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

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?