この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
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