【RSpec】allow_any_instance_ofを使ったテストコードをinstance_doubleを使ってリファクタリングする

# 環境

- Rails 5.2.3
- RSpec 3.8.2

# コード例

以下のコードに対して、`allow_any_instance_of`（非推奨）を使って書いていたテストコードをリファクタリングします。

```ruby
class Order
  def paid?
    payments.last&.libra_payment&.paid?
  end
end
```

# リファクタリング前のテストコード（`allow_any_instance_of`使用）

```ruby
  describe '#paid?' do
    # allow_any_instance_of を使って mock しているため paid_by_libra_payment 部分は何の trait でも良い。
    subject { create(:order, :paid_by_libra_payment) }

    context 'when paid? of LibraPayment returns true' do
      # rubocop:disable RSpec/AnyInstance
      before do
        allow_any_instance_of(LibraPayment).to receive(:paid?).and_return(true)
      end

      it { is_expected.to be_paid }
    end

    context 'when paid? of LibraPayment returns false' do
      before do
        allow_any_instance_of(LibraPayment).to receive(:paid?).and_return(false)
      end
      # rubocop:enable RSpec/AnyInstance

      it { is_expected.not_to be_paid }
    end
  end
```

> [RSpec::AnyInstance](https://www.rubydoc.info/gems/rubocop-rspec/1.6.0/RuboCop/Cop/RSpec/AnyInstance) の警告
>
> Pefer instance doubles over stubbing any instance of a class
> クラスのインスタンスをstubするのではなく、`instance_double`を使うこと

# リファクタリング後のテストコード（`instance_double`使用）

```ruby
  describe '#paid?' do
    subject(:order) { create(:order) }

    let(:mock_libra_payment) { instance_double(LibraPayment) }
    let(:mock_payment) { instance_double(Payment) }

    before do
      allow(mock_payment).to receive(:libra_payment).and_return(mock_libra_payment)
      allow(order.payments).to receive(:last).and_return(mock_payment)
    end

    context 'when paid? of LibraPayment returns true' do
      before do
        allow(mock_libra_payment).to receive(:paid?).and_return(true)
      end

      it { is_expected.to be_paid }
    end

    context 'when paid? of LibraPayment returns false' do
      before do
        allow(mock_libra_payment).to receive(:paid?).and_return(false)
      end

      it { is_expected.not_to be_paid }
    end
  end
```

# リファクタリング後のテストコード（`receive_message_chain`使用）

`receive_message_chain`を使って書くことも可能ですが、[RSpec::MessageChain](https://www.rubydoc.info/gems/rubocop-rspec/1.7.0/RuboCop/Cop/RSpec/MessageChain) の警告が出ます。
前述の通り、`instance_double`を使いましょう。

```ruby
  describe '#paid?' do
    subject(:order) { create(:order) }

    before do
      # この例では OpenStruct を使って mock_libra_payment を定義しているが、それぞれの context 内で
      # この allow 行を書き、それぞれ
      # .and_return(true) や .and_return(false)
      # としても良い。
      allow(order.payments).to receive_message_chain(:last, :libra_payment).and_return(mock_libra_payment)
    end

    context 'when paid? of LibraPayment returns true' do
      let(:mock_libra_payment) { OpenStruct.new(paid?: true) }

      it { is_expected.to be_paid }
    end

    context 'when paid? of LibraPayment returns false' do
      let(:mock_libra_payment) { OpenStruct.new(paid?: false) }

      it { is_expected.not_to be_paid }
    end
  end
```

# 補足

以下のようなコードで、`user` を mock して任意の `authenticated?` の結果を返す方法。

- コード

```ruby
user = User.find(user_id)
user.authenticated?
```

- テストコード

```ruby
user_double = instance_double(User)
allow(User).to receive(:find).and_return(user_double)
allow(user_double).to receive(:authenticated?).and_return(true)
```