16
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

posted at

Organization

AWS SDK for Rubyのクライアントをスタブ化する

AWS SDKを使用する機能をテストする際、そのままテストすると「都度AWSへのアクセスが発生してしまう」ということを熟慮しなければなりません。テストの内容・頻度によっては思わぬ額の料金請求が発生したり、AWS側からペナルティを受けたりする懸念があります。

本記事では、上記問題の回避策としてAWS SDKを使用する機能をテストする際にクライアントをスタブ化する方法をまとめます。

例題

以下のように、AWSへアクセスする関数start_instancesをテストするケースを想定します。

aws_sample.rb
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によって生成される全クライアントがスタブとなるため、特定のクライアントのみをスタブ化することはできない点に注意が必要です。

aws_sample.rb
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生成時のオプションを変更する

クライアント生成時の引数を変更することで、生成されるクライアントをスタブ化することができます。

aws_sample.rb
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

スタブの戻り値を設定する

上記方法で作成したスタブは、メソッド呼び出し時に何かしらの戻り値を返すものの、その内容はいわゆる空の応答です。このため、以下のように応答の内容を参照する場合において意図しない動作となります。

aws_sample.rb
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引数に返して欲しい応答の内容を設定します。

aws_sample.rb
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のクライアントは上記の通り容易にスタブ化することができます。
スタブを上手く活用して、テスト時間短縮、不要なアクセス回避を心がけるようにしましょう。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
16
Help us understand the problem. What are the problem?