16
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-04-12

環境

  • Rails 5.2.3
  • RSpec 3.8.2

コード例

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

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

リファクタリング前のテストコード(allow_any_instance_of使用)

  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 の警告

Pefer instance doubles over stubbing any instance of a class
クラスのインスタンスをstubするのではなく、instance_doubleを使うこと

リファクタリング後のテストコード(instance_double使用)

  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 の警告が出ます。
前述の通り、instance_doubleを使いましょう。

  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? の結果を返す方法。

  • コード
user = User.find(user_id)
user.authenticated?
  • テストコード
user_double = instance_double(User)
allow(User).to receive(:find).and_return(user_double)
allow(user_double).to receive(:authenticated?).and_return(true)
16
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?