ActionCableのRspecでメッセージの送受信をテストする

# ActionCableのRspecでメッセージの送信から配信までをテスト

ActionCableは最近できた機能なのであまり情報がなく、Rspecの実装に非常に時間がかかったのでアウトプット。
同じようにお困りの方がいれば参考にしてほしいです。

今回は以下のメソッドによるメッセージの送受信をテストします。

```ruby
class MessageChannel < ApplicationCable::Channel

  # メッセージを保存し、ブロードキャストするためのアクション
  def post(data)
    @chat_group = ChatGroup.find(params['chat_group_id'])
    @message = @chat_group.messages.build(text: data['message'])
    if @message.save 
     ActionCable.server.broadcast "message_channel_#{params['chat_group_id']}", message: @message, group: @chat_group
    end
  end
```

このメソッドのテストで重要なのは以下の2つ。

- textカラムの値を送るとメッセージが保存されること
- 接続しているチャネルにメッセージが配信されること

これらがテストできればOKとしました。

実際のテストはこうなりました。

```ruby
require 'rails_helper'

RSpec.describe MessageChannel, type: :channel do
  before do
    @chat_group = create(:chat_group)
    stub_connection 
  end

  describe "メッセージの送信" do
    before do
      @message = build(:message, chat_group_id: @chat_group.id) #postメソッド内でメッセージを保存するのでここではbuildするだけ
    end
    
    context "送信成功" do
      it "グループが選択されている状態でメッセージを送信するとメッセージが一つDBに保存される" do
          subscribe(chat_group_id: @chat_group.id)
          expect(subscription).to be_confirmed
          expect do 
           perform :post, message: @message.text
          end. to change(Message, :count).by(1) 
      end

      it "グループが選択されている状態でメッセージを送信するとメッセージが接続しているチャネルに配信される" do
          subscribe(chat_group_id: @chat_group.id)
          expect(subscription).to be_confirmed
          expect do 
           perform :post, message: @message.text
          end. to have_broadcasted_to("message_channel_#{@chat_group.id}").with{ |data|
            expect(data['message']['text']).to eq @message.text
          }
      end
    end 

    context "送信失敗" do
      it "グループが選択されていない状態でメッセージを送信するとメッセージが保存できない" do
          subscribe(chat_group_id: nil)
          expect do 
           perform :post, message: @message.text
          end. to raise_error RuntimeError #Must be subscribed! というエラーが出ることを検証
      end
    end

  end
end

```

詳しくみていきます

## beforeブロック

メソッド内で保存処理を行うのでbeforeブロックではインスタンスの生成を行った。

```ruby
before do
      @message = build(:message, chat_group_id: @chat_group.id) #postメソッド内でメッセージを保存するのでここではbuildするだけ
    end
```

## 正常形

正常形は2つexampleを設定

```ruby
context "送信成功" do
      it "グループが選択されている状態でメッセージを送信するとメッセージが一つDBに保存される" do
          subscribe(chat_group_id: @chat_group.id)
          expect(subscription).to be_confirmed
          expect do 
           perform :post, message: @message.text
          end. to change(Message, :count).by(1) 
      end

      it "グループが選択されている状態でメッセージを送信するとメッセージが接続しているチャネルに配信される" do
          subscribe(chat_group_id: @chat_group.id)
          expect(subscription).to be_confirmed
          expect do 
           perform :post, message: @message.text
          end. to have_broadcasted_to("message_channel_#{@chat_group.id}").with{ |data|
            expect(data['message']['text']).to eq @message.text
          }
      end
    end 
```

1つめはデータの保存ができることを確認。2つめは接続しているチャネルに保存したメッセージが配信されることを確認。1つめはよく見るデータの保存処理。request_specに書き慣れている人なら余裕だと思う。

2つめはかなりややこしい。ここで8時間かかった。

```ruby
expect do 
  perform :post, message: @message.text
end. to have_broadcasted_to("message_channel_#{@chat_group.id}").with{ |data|
  expect(data['message']['text']).to eq @message.text
  expect(data['message']['id']).not_to eq nil #idが存在していることを確認
  }
```

この部分はperformでpostメソッドを動かしたときにbroadcastされるdataの内容をブロック変数dataで受け取っている。ブロック内でdataのidがnilではなく、かつtextが送ったデータと一致することを検証。
こうすることで送ったデータが保存できていることを検証できる。

ちなみにdataの中身をbinding.pryで確認すると以下のようになる。

```terminal
   51:           expect do 
    52:            perform :post, message: @message.text
    53:           end. to have_broadcasted_to("message_channel_#{@chat_group.id}").with{ |data|
    54:             expect(data['message']['text']).to eq @message.text
    55:             
 => 56:             binding.pry
    57:             
    58:           }
    59:       end
    60:     end 
    61: 

[1] pry(#<RSpec::ExampleGroups::MessageChannel::Nested::Nested>)> data
=> {"message"=>{"id"=>161, "text"=>"test message", "chat_group_id"=>618, "created_at"=>"2021-02-18T23:53:20.494Z", "updated_at"=>"2021-02-18T23:53:20.494Z"},
```

これは実行環境でbroadcastされるdataと一致している。

```
[ActionCable] Broadcasting to message_channel_35: {:message=>#<Message id: 8, text: "test", chat_group_id: 35, created_at: "2021-02-18 23:57:33", updated_at: "2021-02-18 23:57:33">}
```


## 異常形
subscribeされていない状態でメッセージを送ろうとするとエラーが発生することを検証。
これもrequest_specとか書き慣れている人であればあまり苦労しない

```ruby
   context "送信失敗" do
      it "グループが選択されていない状態でメッセージを送信するとメッセージが保存できない" do
          subscribe(chat_group_id: nil)
          expect do 
           perform :post, message: @message.text
          end. to raise_error RuntimeError #Must be subscribed! というエラーが出ることを検証
      end
    end
```

これにてpostメソッドのテスト実装完了。一つのexampleに8時間とかかけたのはほんまに大変で泣きそうでした。

そもそもなんのメソッドが使えるのかがわからないので必死にbinding.pryで調べたりしてた。ActionCableは最近できた機能なので情報が少なく、検索も一苦労でした。頑張りました。。。。。


### 参考

- channel_specの書き方<br>
  https://relishapp.com/rspec/rspec-rails/docs/channel-specs/channel-spec
- have_broadcastedマッチャ<br>
  https://relishapp.com/palkan/action-cable-testing/docs/matchers/have-broadcasted-matcher