背景
Amazon SQS。自分のサービスにメッセージキューサービスをとても簡単に組み込めて便利。
http://aws.amazon.com/jp/sqs/
しかし、いざそれを使った機能のspecを書こうと思うと、「外部サービスだからstub書かなきゃ」という辛みと、「結合テスト的に実際にAPI叩きたいし」という欲求が出てきて、どうしたらいいのよ 、という感じになる。
そりゅーしょん
環境用意すれば良いじゃない!
ElasticMQ
https://github.com/adamw/elasticmq
こちら、Amazon SQS-compatible interfacesという素晴らしい特徴を持ったメッセージキューシステムです。
これをローカルに立ち上げれば、そもそも普通の開発の時からローカルでキューシステム使った検証できるし、何よりrspecからキューへの登録、取得などのテストもちゃんとAPIを叩いた上で通すことができる。
注意
以下は、rails環境でrspecを利用することを想定しています。
準備
まずは、ElasticMQを用意。ローカルに普通にダウンロードしても良いけど、CircleCIとかでの利用を見越すとDockerコンテナとして用意しておくのが良さげ。
FROM java:8
# Create working space
RUN mkdir /var/elasticMQ
WORKDIR /var/elasticMQ
# Default port for SQS Local
EXPOSE 9324
# Get the package from Amazon
RUN wget -q https://s3-eu-west-1.amazonaws.com/softwaremill-public/elasticmq-server-0.8.8.jar
# Default command for image
ENTRYPOINT ["/usr/bin/java", "-Djava.library.path=.", "-jar", "elasticmq-server-0.8.8.jar"]
spec_helper
spec_helper
のほうに、以下の設定を書く。
IPはboot2dockerでelasticmqを立ち上げたときの値なので、必要に応じて127.0.0.1
とかにすると良い。
ここではサンプルなので、キューの名前も決め打ちで一つだけにしているけど、実際には設定ファイルから読みこんだ値を使うなどよしなに。
# rspec内では常に以下のクライアントを使う
sqs_client = Aws::SQS::Client.new(
region: 'dummy-sqs',
endpoint: "http://192.168.59.103:9324",
access_key_id: 'dummy',
secret_access_key: 'dummy',
)
regionやアクセスキーなどの値は適当な値でも大丈夫そう(今のところ)。aws-sdkのversionが上がったりするとその辺のvalidationが走ってダメになるかも。今回試したのは2.0.42。
# 事前にqueueを作っておく
# 必要に応じて特定のspecでのみキューを作成するなどにしても良い
config.before(:suite) do
# 都度都度Clientをstubするのが面倒なので`new`自体をstubする
allow(::Aws::SQS::Client).to receive(:new).and_return(sqs_client)
sqs_client.create_queue(queue_name: "sample_jobs")
# http://192.168.59.103:9324/queue/sample_jobs
# が作成したqueue_urlとなる
end
# テストが終わったらqueueのメッセージを消す
# purge処理は即時実行される訳ではないので、次のspec実行時にメッセージが残っている可能性あり
config.after(:each) do
sqs_client.purge_queue(queue_url: "http://192.168.59.103:9324/queue/sample_jobs")
end
spec
以上の準備ができたら、以下のようなモデルとspecを書いてみる。
class JobRegister
class << self
def client
Aws::SQS::Client.new(
region: 'ap-northeast-1' # 実際には決め打ちにすんなよ!
)
end
def register message
client.send_message(
queue_url: "http://192.168.59.103:9324/queue/sample_jobs", # この辺も実際にはよしなにね!
message_body: {
message: message,
}.to_json
)
end
end
end
end
require 'spec_helper'
describe JobRegister do
describe '.register' do
it 'ジョブが登録されていること'
described_class.register "hello"
message = described_class.client.receive_message(queue_url: "http://192.168.59.103:9324/queue/sample_jobs").messages.first
expect(JSON.parse(message.body)["message"]).to eq "hello"
end
end
end
というspecが通るようになる。
注意
queue_urlにlocalhost
を使うには、
http://dev.classmethod.jp/server-side/ruby-on-rails/ruby_aws-sdk-core_elasticmq_sqs/
に記載のある通り、パッチを当てる必要がある。しかし、aws-sdkの実装を見る限り"."でsplitした結果からregionを取得してるようなので、"127.0.0.1"のように"."さえ入っていればパッチは当てなくても動いた。
まとめ
とりあえずrspecからelasticmqを叩けた。CircleCIにコンテナを持って行けば、CI環境で結合テストも可能。