AWS SDKを使用する機能をテストする際、そのままテストすると「都度AWSへのアクセスが発生してしまう」ということを熟慮しなければなりません。テストの内容・頻度によっては思わぬ額の料金請求が発生したり、AWS側からペナルティを受けたりする懸念があります。
本記事では、上記問題の回避策としてAWS SDKを使用する機能をテストする際にクライアントをスタブ化する方法をまとめます。
例題
以下のように、AWSへアクセスする関数start_instances
をテストするケースを想定します。
require 'aws-sdk'
require 'minitest/autorun'
# テスト対象の関数
def start_instances
# EC2のクライアントを生成する
credential = Aws::AssumeRoleCredentials.new(region: REGION, client: Aws::STS::Client.new,
role_arn: ROLE_ARN, role_session_name: 'sample_client')
client = Aws::EC2::Client.new({credentials: credential})
# EC2のインスタンスを起動する
resp = client.start_instances(instance_ids: [EC2_INSTANCE_ID])
end
# テストコード
class Ec2StartTest < Minitest::Test
def test_ec2_start
resp = start_instances
assert resp != nil
end
end
このまま実行してもテストはPASSします。
ただし、テストを実行するごとにclient.start_instances
によるAWSとの通信が発生します。
AWSとの通信を発生させずに、テストする方法を考えてみましょう。
解決方法
1. Aws.configを使用する
事前にAws.config[:stub_responses] = true
を実行することで、 Aws::EC2::Client.new
で生成されるクライアントをスタブ(AWSとは通信をせずに結果を応答するオブジェクト)とすることができます。
「コードを1行追加するだけ」とお手軽な方法である反面、AWS SDKによって生成される全クライアントがスタブとなるため、特定のクライアントのみをスタブ化することはできない点に注意が必要です。
require 'aws-sdk'
require 'minitest/autorun'
if Rails.env.test?
Aws.config[:stub_responses] = true # !!!追加
end
# テスト対象の関数
def start_instances
# EC2のクライアントを生成する
credential = Aws::AssumeRoleCredentials.new(region: REGION, client: Aws::STS::Client.new,
role_arn: ROLE_ARN, role_session_name: 'sample_client')
client = Aws::EC2::Client.new({credentials: credential})
# EC2のインスタンスを起動する
resp = client.start_instances(instance_ids: [EC2_INSTANCE_ID])
end
# テストコード
class Ec2StartTest < Minitest::Test
def test_ec2_start
resp = start_instances
assert resp != nil
end
end
2. Client生成時のオプションを変更する
クライアント生成時の引数を変更することで、生成されるクライアントをスタブ化することができます。
require 'aws-sdk'
require 'minitest/autorun'
# テスト対象の関数
def start_instances
# EC2のクライアントを生成する
credential = Aws::AssumeRoleCredentials.new(region: REGION, client: Aws::STS::Client.new,
role_arn: ROLE_ARN, role_session_name: 'sample_client')
if Rails.env.test?
client = Aws::EC2::Client.new(stub_responses: true) # !!!追加
else
client = Aws::EC2::Client.new({credentials: credential})
end
# EC2のインスタンスを起動する
resp = client.start_instances(instance_ids: [EC2_INSTANCE_ID])
end
# テストコード
class Ec2StartTest < Minitest::Test
def test_ec2_start
resp = start_instances
assert resp != nil
end
end
スタブの戻り値を設定する
上記方法で作成したスタブは、メソッド呼び出し時に何かしらの戻り値を返すものの、その内容はいわゆる空の応答です。このため、以下のように応答の内容を参照する場合において意図しない動作となります。
require 'aws-sdk'
require 'minitest/autorun'
if Rails.env.test?
Aws.config[:stub_responses] = true
end
# テスト対象の関数
def start_instances
# EC2のクライアントを生成する
credential = Aws::AssumeRoleCredentials.new(region: REGION,
client: Aws::STS::Client.new,
role_arn: ROLE_ARN,
role_session_name: 'sample_client')
client = Aws::EC2::Client.new({credentials: credential})
# EC2のインスタンスを起動する
resp = client.start_instances(instance_ids: [EC2_INSTANCE_ID])
end
# テストコード
class Ec2StartTest < Minitest::Test
def test_ec2_start
resp = start_instances
assert resp != nil
assert_equal 'i-xxxx', resp.starting_instances[0].instance_id # !!!応答を参照する処理
end
end
有効な応答を返してもらう時はclient.stub_responses
を事前に呼び出し、第1引数に呼び出すメソッドのシンボルを、第2引数に返して欲しい応答の内容を設定します。
require 'aws-sdk'
require 'minitest/autorun'
if Rails.env.test?
Aws.config[:stub_responses] = true
end
# テスト対象の関数
def start_instances
# EC2のクライアントを生成する
credential = Aws::AssumeRoleCredentials.new(region: REGION,
client: Aws::STS::Client.new,
role_arn: ROLE_ARN,
role_session_name: 'sample_client')
client = Aws::EC2::Client.new({credentials: credential})
if Rails.env.test?
# スタブに応答を設定する
client.stub_responses(:start_instances, { starting_instances: [ { instance_id: 'i-xxxx' } ] }) # !!!追加
end
# EC2のインスタンスを起動する
resp = client.start_instances(instance_ids: [EC2_INSTANCE_ID])
end
# テストコード
class Ec2StartTest < Minitest::Test
def test_ec2_start
resp = start_instances
assert resp != nil
assert_equal 'i-xxxx', resp.starting_instances[0].instance_id # !!!応答を参照する処理
end
end
注意
上記方法はクライアントをスタブ化する方法です。
モックではない(≒クライアントに対するメソッド呼び出し回数や引数の検証は実施しない)点に注意しましょう。
まとめ
AWS SDK for Rubyのクライアントは上記の通り容易にスタブ化することができます。
スタブを上手く活用して、テスト時間短縮、不要なアクセス回避を心がけるようにしましょう。