環境
- 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
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)