[Ruby][aws-sdk-core][ElasticMQ]SQSへの接続をローカルでテストする
はじめに
SQSへ接続するアプリを作成する場合、特にRSpecなどでテストをする際など、ローカルにSQSのダミーを立てて接続したいことがあるかと思います。このような場合の方法として、ダミーのSQSとしてElasticMQを使い、アプリ本体のソースからはAWS上のSQSに接続・RSpecからはローカルのElasticMQに接続するというやり方があります。今回、そのサンプルを作成してみました。
※SQSへの接続に、aws-sdk-coreを使用しました。後述しますが、一部、aws-sdk-coreへのモンキーパッチを当てた部分があります。この部分に関しては、aws-sdk-coreのバージョンが異なれば必要ないかもしれません(今回のバージョンは"2.0.17")。またaws-sdk-coreはV1のAWS SDK for Rubyとは違うものであることにも注意してください。
事前準備
1.ElasticMQ
ElasticMQのサイトよりjarをダウンロードしてください。そして以下のコマンドでElasticMQを起動しておきます。
$ java -jar elasticmq-server-0.8.5.jar
プロジェクトの作成
$ bundle init 等でGemfileを用意した後、必要となるgemを記述し、$ bundle installを行います。
Gemfile
source 'https://rubygems.org' gem 'aws-sdk-core' group :development,:test do gem 'rspec' gem 'pry-byebug' end
SQSに接続するための「aws-sdk-core」、テストをするための「rspec」をインストールします。(「pry-byebug」はデバッグで使いたい方のみ入れてください。)
ソースコード
AWS上のSQSに対して以下の処理を行うソースと、それをテストするRSpecを作成しました。
- キューの作成
- メッセージ送信
- メッセージ受信
- メッセージ削除
- キューの削除
SQSへの処理
以下、上述した処理を行うソースです。
elasticmq_sample.rb
require 'aws-sdk-core' require 'aws-sdk-core/sqs_queue_urls' module ElasticmqSample class SqsSample def create_queue(queue_name) queue = client.create_queue( queue_name: queue_name, ) end def send_message(queue_url, body) client.send_message( queue_url: queue_url, message_body: body ) end def receive_message(queue_url) client.receive_message( queue_url: queue_url ) end def delete_message(queue_url, receipt_handle) client.delete_message( queue_url: queue_url, receipt_handle: receipt_handle, ) end def delete_queue(queue_url) client.delete_queue( queue_url: queue_url ) end private def client @client ||= Aws::SQS::Client.new( region: 'us-west-2', profile: 'personal' ) end end end
上から2行で必要なモジュールを取り込んでいます。「aws-sdk-core/sqs_queue_urls」については、後述するモンキーパッチです。その下ではキュー、メッセージの操作を行うメソッドをpublicで定義しています。
一番下の「client」メソッドですが、SQSへ接続を行うためのClientオブジェクトを生成しています。実際にAWS上のSQSに対して処理を行うため、ここでは「本当」にAWSへ接続するよう定義しています。
尚、この例では~/.aws/credentialsに「personal」というプロファイル名でaccess_key_id, secret_access_keyを定義しています。
RSpec
上記の処理をテストするためのRSpecです。
elasticmq_sample_spec.rb
require 'spec_helper' require 'aws-sdk-core' describe ElasticmqSample do QUEUE_NAME = 'elasticmq_sample_queue' MESSAGE = 'test message.' let(:sqs_sample) { ElasticmqSample::SqsSample.new } let(:client) { Aws::SQS::Client.new({ region: 'us-west-2', endpoint: 'http://localhost:9324', profile: 'personal' }) } describe 'queue create and delete' do queue = nil receipt_handle = '' # beforeメソッドをコメントアウトすると、AWS上のSQSに接続する before(:each) do allow(sqs_sample).to receive(:client).and_return(client) end it 'create queue' do queue = sqs_sample.create_queue(QUEUE_NAME) expect(queue[:queue_url].include?(QUEUE_NAME)).to eq(true) end it 'send message' do result = sqs_sample.send_message(queue[:queue_url], MESSAGE) expect(result[:message_id]).not_to eq(nil?) end it 'receive message' do result = sqs_sample.receive_message(queue[:queue_url]) receipt_handle = result.messages[0][:receipt_handle] count = result.messages.count expect(count).not_to eq(0) end it 'delete message' do result = sqs_sample.delete_message(queue[:queue_url], receipt_handle) expect(result.nil?).to eq(false) end it 'delete queue' do result = sqs_sample.delete_queue(queue[:queue_url]) expect(result.nil?).to eq(false) end end end
9〜15行目で、ローカルのSQSであるElasticMQに接続するClientオブジェクトを生成しています。「endpoint〜」で接続先をローカルにしているだけですね。
このClientオブジェクトを使用するよう、23行目で設定しています。「allow」を使い、テスト対象となるソース(elasticmq_sample.rb)の「client」メソッドが返却するオブジェクトとして、RSpec内で生成したClientオブジェクトを指定しています。
なのでこの23行目をコメントアウトすると、テスト対象となるソース内で生成するClientオブジェクトが使われるため、テストの実行時にもAWS上のSQSへ接続が行われます。
モンキーパッチ
最後にモンキーパッチについてです。今回使用したaws-sdk-core 2.0.17では、ローカルのElasticMQに対して「send message」を行ったときに以下のエラーが発生しました。
ArgumentError: invalid queue url `http://localhost:9324/queue/elasticmq_sample_queue'
原因はaws-sdk-coreの以下の部分です。
〜/ruby/2.1.0/gems/aws-sdk-core-2.0.17/lib/aws-sdk-core/plugins/sqs_queue_urls.rb
module Aws module Plugins # @api private class SQSQueueUrls < Seahorse::Client::Plugin class Handler < Seahorse::Client::Handler (中略) def update_region(context, url) if region = url.to_s.split('.')[1] context.config = context.config.dup context.config.region = region context.config.sigv4_region = region else raise ArgumentError, "invalid queue url `#{url}'" end end end (中略) end end end [/ruby]
この「update_region」メソッドですが、urlよりregion名を取得し、nilの場合はエラーを発生させています。「本物」のSQSに接続する場合のurlは「https://sqs.us-west-2.amazonaws.com/xxxx/elasticmq_sample_queue」となりregionを取得できますが(この場合では「us-west-2」)、ローカルのElasticMQの場合は「http://localhost:9324」となりregionを取得できません。このためエラーが発生していました。
このエラーを回避するため、以下のようなモンキーパッチを作成しました。
aws-sdk-core/sqs_queue_urls.rb
module Aws module Plugins # @api private class SQSQueueUrls < Seahorse::Client::Plugin class Handler < Seahorse::Client::Handler def update_region(context, url) if region = url.to_s.split('.')[1] context.config = context.config.dup context.config.region = region context.config.sigv4_region = region elsif url.include?('localhost') return else raise ArgumentError, "invalid queue url `#{url}'" end end end end end end [/ruby]
選択してある所が、今回追加したソースです。urlに「local」がある(つまりローカルのElasticMQに接続したい)場合、contextを上書きせずに処理を終了しています。
まとめ
RSpecのテストを実行する際に、AWS上のSQSに接続する代わりにローカルのElasticMQに接続するだけでしたが、結構手間取りました。「allow」によるテスト対象の置き換え、モンキーパッチによるgemの修正など、RubyやRSpecの面白い部分を使うことが出来たように思います。
キューの作成・削除、メッセージの送受信・削除と限定した操作しか行っていませんが、SQSをローカルでテストする際に少しでも役に立てれば幸いです。
備考
今回のソースコードは以下のGithubに上げてあります。必要な方は、参考にしてください。
elasticmq_sample