TL;DR
RailsのメソッドをRspecでテストする際に、Elasticsearchを使ったモデルに対してのテストの実行時にのみ、実行前後でElasticsearchのインデックスを初期化する処理を入れる方法です。
やりたかったこと
elasticsearch-rails
gemを用いたRailsのアプリケーションにおいて、下記のようにElascticsearchを用いて検索や絞り込みを行うメソッドを実装しました。
class SampleClass < ApplicationRecord
def search(filter_ids, target_ids: [], min_price: nil, max_price: nil, size: 200)
__elasticsearch__.search({
size: size,
query: build_query(filter_ids, target_ids, min_price, max_price)
})
en
end
こちらのメソッドに対して、RSpecでテストを書きたかった際にどのようにするか迷ってしまいました。
今回はモデルごとにElasticsearchのインデックスを初期化する方法を使って実装したので、そちらを紹介したいと思います。
テスト環境で常に使えないか
Elasticsearchではインデックスを作成して検索を実行するのですがこちらのインデックスを複数のテストケースで共有してしまうと、別のテストで作成・更新されたインデックスに対してテストがされてしまうため、実際にテストしたい内容がテストできていなかったり、テスト対象のメソッドは正しいのに意図しない結果が返ってくるというようなことが起きてしまいます。
そのためテストの前後でインデックスの初期化を行いたいのですが、Elasticsearchを使っていないメソッドの実行前後でも初期化の処理を書く必要はなく、実行時間の観点からもなるべくインデックスの初期化は最小限にしたい意図があります。
Elasticsearchを使用しているモデルのテストでのみインデックスの初期化を行う
そこで今回は下記の手順で設定し、Elasticsearchを使っているテストやモデルに対してのみインデックスの初期化を行うようにしました、
これ以前にElasticsearchのDockerコンテナは設定されているものとします。
1. 環境変数に設定
使用するElasticserachコンテナのhostとportを環境変数に用意します
elasticsearch:
host: <%= ENV['ELASTICSEARCH_HOST'] || 'localhost' %>
port: <%= ENV['ELASTICSEARCH_PORT'] || '9200' %>
2. テスト環境でもElasticsearchコンテナを起動するようにする
1で設定した環境変数のコンテナをテスト環境でも読み込むようにします。
RailsでのElasticsearchクライアントの初期化についての詳細はelasticsearch-rubyに書いてあります。
Elasticsearch::Model.client = Elasticsearch::Client.new(
hosts: [
{
host: Settings.elasticsearch.host,
port: Settings.elasticsearch.port
}
],
log: true
) if Rails.env.test?
3. spec_helper.rbにインデックスの初期化処理を追加
RSpecを導入時に作成されるspec_helper.rbにて、それぞれのテストでの共通の処理を記述することができます。
RSpecで定義できるmetadataに応じてテストの前にlasticsearchのインデックスを作成、テスト後にElacticsearchのインデックスを削除する処理を行うようにしました。
config.before(:each) do |example|
Product.__elasticsearch__.create_index! if example.metadata[:elasticsearch]
end
config.after(:each) do |example|
Product.__elasticsearch__.delete_index! if example.metadata[:elasticsearch] && Product.__elasticsearch__.index_exists?
end
独立してテストできようになった
こちらの処理を書いたことで、テストするモデルごとにテストの実行前後でElasticsearchのインデックスを初期化することができ、それぞれのテストを独立して実行することができます。
RSpec.describe SampleClass, type: :model, elasticsearch: true do
end
応用:Github Actionsを使ってCIに組み込む
また、今回のようにRspecのmetadetaを使った分岐使用すれば、Github Actionsにも組み込むことができます。
workflowの中でElasticsearchのコンテナを用意して、テストの実行環境から読み込めるようにすればOKです。
jobs:
rspec:
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:7.16.3
env:
discovery.type: single-node
ports:
- 9200:9200
steps:
- name: run rspec
env:
ELASTICSEARCH_PORT: ${{ job.services.elasticsearch.ports['9200'] }}
参考