elasticmqを使ったsqsとの連携テスト


背景

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
spec/models/job_register_spec.rb
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環境で結合テストも可能。