【Rails】PayjpのRSpecテスト、モックを使用してコントローラテストを実装する

# はじめに
payjpなどの外部APIと通信するRailsアプリの場合、
テストする時には実際に通信が走ってしまっては困ります。
今回は、ダミー(テストダブル)を作成して、ダミーのモックを返すようにすることで、コントローラのテストを実装します。

実装のサンプルコードだけ知りたい場合は、【サンプルコード】の項目までスクロールしてください。

# 普通にテストを実装すると
コントローラーのテストでは一般に以下を実装します。

- コントローラーのアクションに対応するビューが正しく表示されるかどうか
- リダイレクトが正しく行われるかどうか
- インスタンス変数が正しく定義されているかどうか

もし、以下のようなコントローラーがあった場合について考えます。

```ruby:card_controller.rb
def index
  customer = Payjp::Customer.retrieve("cus_xxxxxxxxxxxxx")
end
```

今回はindexのアクションはindexのビューが表示されることをテストします。
コントローラのスペックでは以下のようになります。

```ruby:card_controller_spec.rb
it "cardの一覧画面（indexアクション）にアクセスすると、indexのビューが表示される" do
  get :index
  expect(response).to render_template("index")
end
```

しかしこのままでは実際のコントローラでPayjp::Customer.retrieve()のコードが走り、
実際のpayjpと通信してしまいます。
(テストなのに、payjpの顧客や売り上げを作成してしまうのはまずいですよね！)

# payjpと通信してしまうのを回避する
通信を回避する方法として、allowというメソッドがあります。
使い方は以下のような感じです。

```ruby
allow(Payjp::Customer).to receive(:create).and_return(true)
```

これはPayjp::Customer.create()という呼び出しがあった際に、trueを返すというものです。
これを使用すると、実際に通信が走ってしまうのを回避できると思います。

# モックとは
モックとは、本物のように振る舞う偽物、結論以下のようなものです。

```ruby
payjp_customer = double("Payjp::Customer")
```

先ほど、allowは最終的にtrueを返しました。
Payjp::Customer.create()ではpayjpの顧客を返すのが理想なため、顧客を返すにはどうしたら良いかというのを考えます。
payjpの実際の顧客データは以下の通りです。

<details><summary>顧客データ</summary><div>

```ruby
{
  "cards": {
    "count": 0,
    "data": [],
    "has_more": false,
    "object": "list",
    "url": "/v1/customers/cus_121673955bd7aa144de5a8f6c262/cards"
  },
  "created": 1433127983,
  "default_card": null,
  "description": "test",
  "email": null,
  "id": "cus_121673955bd7aa144de5a8f6c262",
  "livemode": false,
  "metadata": null,
  "object": "customer",
  "subscriptions": {
    "count": 0,
    "data": [],
    "has_more": false,
    "object": "list",
    "url": "/v1/customers/cus_121673955bd7aa144de5a8f6c262/subscriptions"
  }
}
```

</div></details>

上記を丸々変数やメソッドにして返すようにしても良いのですが、rspecには
便利なメソッドが用意されています。

`テストダブル - double()`

です。
テストダブルは`double("Payjp::Customer")`のように使用します。こうすると、Payjp::Customerオブジェクトのダミーを用意することができます。(つまりモックを作ることができる)

これを使用すると以下のようにできます。

```ruby
payjp_customer = double("Payjp::Customer")
allow(Payjp::Customer).to receive(:create).and_return(payjp_customer)
```

# 一歩応用すると
別のパターンを考えます。
もしコントローラに以下のようなコードがあったとします。

```ruby:controller
customer = Payjp::Customer.create(....)
customer.cards.retrieve(....)
```

上記コードだとpayjpとの通信は、３回起きます。
- Payjp::Customer.create()
- customer.cards
- cards.retrieve()

なので、テストダブル、allow文は以下のようになります。

```ruby:spec
payjp_customer = double("Payjp::Customer")
payjp_list = double("Payjp::ListObject")
payjp_card = double("Payjp::Card")
allow(Payjp::Customer).to receive(:create).and_return(payjp_customer) 
allow(payjp_customer).to receive(:cards).and_return(payjp_list) 
allow(payjp_list).to receive(:retrieve).and_return(payjp_card) 
```

ここで注目して欲しいのはallow文です。
allow文は通信の回避という側面以外に、メソッドの登録をしているとも取れます。
`double("Payjp::Customer")`で作成したオブジェクトはPayjp::Customerオブジェクトですが、実際のオブジェクトとは異なり、メソッドなどが全く定義されていないです。
(double("Payjp::Customer").cardsとしてもcardsというメソッドはありませんとエラーが出るでしょう。)

`allow(payjp_customer).to receive(:cards).and_return(payjp_list)`

とすることでpayjp_customerというテストダブルに、cardsというメソッドを教えていると捉えてもオッケーです。

# 【サンプルコード】 モックを使用したテストの実装

## コントローラーのサンプル
より実践的な一例です。(*実際はもう少し、createアクションが完結になるよう努力しています。)
<details><summary>サンプルコード</summary><div>

```rb
def create
  Payjp.api_key = get_payjp_key
  customer = Payjp::Customer.create
  @credit = create_payjp_card(current_user, customer)
  if @credit.save
    redirect_to credits_path
  else
    render "new"
  end  
end

private
  def create_payjp_card(user, customer)
    customer.cards.create(card: token_parmas[:token])
    credit = Credit.new(
      user_id: user.id,
      customer_id: customer.id
    )
  end
```
</div></details>

## specのサンプル

<details><summary>サンプルコード</summary><div>

```rb
context "createアクションにアクセスした時" do
  before do
    payjp_customer = double("Payjp::Customer")
    payjp_list = double("Payjp::ListObject")
    payjp_card = double("Payjp::Card")
    allow(Payjp::Customer).to receive(:create).and_return(payjp_customer)        
    allow(payjp_customer).to receive(:cards).and_return(payjp_list)        
    allow(payjp_list).to receive(:create).and_return(payjp_card) 
    allow(payjp_customer).to receive(:id).and_return("cus_xxxxxxxxxxxxx")
  end
  it "@creditが定義されていること" do
    post :create, params: {token: "tok_xxxxxxxx"}
    credit = create(:credit, user_id: user.id, customer_id: "cus_xxxxxxxxxxxxx")
    expect(assigns(:credit).customer_id).to eq(credit.customer_id)
  end
end
```
</div></details>

# まとめ
モックの作成に、supportファイルを作成している方もいますが、私はdoubleを用いたテストの仕方をお勧めします。

ただし、allow文を使いすぎると、モックがきちんと作れているかのテストになってしまう気がします。railsではcontrollerテストがあまり推奨されていないように、他のテストでバリューを出すほうがいい気がします。

<details><summary>supportファイルでの実装
</summary><div>

```ruby:support/payjp_mock.rb
module PayjpMock
  def self.payjp_mock_data
    {
      "cards": {
        "count": 0,
        "data": [],
        "has_more": false,
        "object": "list",
        "url": "/v1/customers/cus_121673955bd7aa144de5a8f6c262/cards"
      },
      "created": 1433127983,
      "default_card": null,
      "description": "test",
      "email": null,
      "id": "cus_121673955bd7aa144de5a8f6c262",
      "livemode": false,
      "metadata": null,
      "object": "customer",
      "subscriptions": {
        "count": 0,
        "data": [],
        "has_more": false,
        "object": "list",
        "url": "/v1/customers/cus_121673955bd7aa144de5a8f6c262/subscriptions"
      }
    }
  end
end
```

</div></details>

# 参考資料
「【Rails】PAY.JPによる決済機能のモックを活用したコントローラーテスト」
https://qiita.com/tiphp452/items/87042d1800af9a312be9
「Rails で Payjp を使って決済システムを導入する」
https://qiita.com/hirotakasasaki/items/794c920016ac7c33da74
「使えるRSpec入門・その3「ゼロからわかるモック（mock）を使ったテストの書き方」」
https://qiita.com/jnchito/items/640f17e124ab263a54dd
「決済処理を統合テストしたい」https://muut.com/i/payjp/general:n8jubya6r7gxcp4398dp9d697ed