Cloud FunctionsのCloudEvent関数でRSpecする

この記事は、[Ateam LifeDesign Advent Calendar 2022](https://qiita.com/advent-calendar/2022/ateam-life-design) カレンダー1の13日目の記事です。

# CloudEvent関数でRSpecがしたかった
Cloud FunctionsのCloudEvent関数のロジックに手を加える機会があったのですが、本番へデプロイするまで動作確認をする術がありませんでした。

CloudEvent関数への改修は初めてでしたので、普段業務で使用しているRSpecでテストコードを書いて安心してリリースが行えるようDockerでのテスト環境の構築から行いました。

この記事ではCloudEvent関数でのRSpecを使ったテストコードの記述例が当時見られなかったので、備忘録を兼ねて書き残したいと思います。


# CloudEvent関数のRSpecの書き方
以下のようなCloudEvent関数を例とする。
内容としてはeventデータが存在したときにFirehoseへデータレコードを配信ストリームに書き込むというもの。

https://docs.aws.amazon.com/sdk-for-ruby/v2/api/Aws/Firehose/Client.html#put_record_batch-instance_method

```ruby
require 'functions_framework'
require 'aws-sdk-firehose'

FunctionsFramework.cloud_event "function" do |event|
  any_datas = event.data['any_datas']
  firehose_client = Aws::Firehose::Client.new(
    access_key_id: 'xxxxx',
    secret_access_key: 'xxxxx',
    region: 'xxxxx'
  )

  any_datas.each do |any_data|
    next if any_data.nil?

    resp = firehose_client.put_record_batch({
      delivery_stream_name: 'xxxxx',
      records: any_data
    })
    logger.info("faild count: #{resp.failed_put_count}")
  end
end
```

RSpecでは以下のようにCloudEvent関数をテストすることが出来ます。

```ruby
require 'rspec'
require 'functions_framework/testing'

describe 'CloudFunction' do
  include FunctionsFramework::Testing
  let(:event) { FunctionsFramework::Testing.make_cloud_event({ 'any_datas' => ['hoge'] }) }
  let(:firehose_client) { double('firehose_client double') }
  let(:file_path) { File.join __dir__, '../app.rb' }
  let(:resp) { double('resp double') }
  before do
    allow(Aws::Firehose::Client).to receive(:new).and_return(firehose_client)
    allow(firehose_client).to receive(:put_record_batch).and_return(resp)
    allow(resp).to receive(:failed_put_count).and_return(0)
  end

  context 'When data exists' do
    it 'function is called' do
      FunctionsFramework::Testing.load_temporary file_path do
        FunctionsFramework::Testing.call_event('function', event)
        expect(firehose_client).to have_received(:put_record_batch).once
      end
    end
  end
end
```

`FunctionsFramework::Testing`が提供するメソッドについて説明します。

## FunctionsFramework::Testing.make_cloud_event
`FunctionsFramework::Testing.make_cloud_event`でCloudEvent関数に渡すためのcloud eventを作成できます。

https://googlecloudplatform.github.io/functions-framework-ruby/v1.2.0/FunctionsFramework/Testing.html#make_cloud_event-instance_method

## FunctionsFramework::Testing.load_temporary
`FunctionsFramework::Testing.load_temporary`でテストするCloudEvent関数のパスとブロックを渡します。

https://googlecloudplatform.github.io/functions-framework-ruby/v1.2.0/FunctionsFramework/Testing.html#load_temporary-instance_method

## FunctionsFramework::Testing.call_event
`FunctionsFramework::Testing.call_event`でテストするCloudEvent関数の名前とcloud eventを渡してCloudEvent関数のテストが出来ます。


https://googlecloudplatform.github.io/functions-framework-ruby/v1.2.0/FunctionsFramework/Testing.html#call_event-instance_method

# 躓いたところ
最初、`FunctionsFramework::Testing.call_event`をsubjectしてCloudEvent関数の結果をテスト出来るだろうと思っていました。

```ruby
# CloudEvent関数は成功か失敗かが分かるよう実行結果を返すよう修正したうえで
subject { FunctionsFramework::Testing.call_event('function', event) }
it { is_expected.to eq('xxxxxx') }
```
しかし、`FunctionsFramework::Testing.call_event`はCloudEvent関数を呼ぶだけで実行結果を返してくれるものではありませんでした。

https://github.com/GoogleCloudPlatform/functions-framework-ruby/blob/a23620f037d86733f8d2eb7fbb2d25fde1c9a194/lib/functions_framework/testing.rb#L170

これに対して`have_received`マッチャを使用してメソッドが呼ばれることを検証することでCloudEvent関数が期待通りの動作することを確認できるテストコードを書くことが出来ました。

`have_received`マッチャは使用したことがなく存在も知らなかったので以下記事を参考にさせていただきました。:pray:

https://qiita.com/_am_/items/398b0782fa3754ff3878

# まとめ
テストコードがあると不具合を未然に防ぐことが出来るだけでなく、安心して今動いている既存機能に手を加えたり、新たな機能を追加することが出来ます。それでけではなく、リファクタリングを促進できたり、メソッドの結合度を下げたテストコードの書きやすいコードを書きやすくなるなどと多くのメリットがあります。

この記事がCloudEvent関数へのテストの導入に少しでもご参考になれば幸いです。:pray:

今回記事を書くにあたって用意したサンプルコードは以下となります。

https://github.com/dOwOd/rspec_for_cloud_function


# 参考文献
https://cloud.google.com/blog/ja/products/application-development/ruby-comes-to-cloud-functions

https://github.com/GoogleCloudPlatform/functions-framework-ruby#documentation