RSpecマッチャーメモ

?メソッドのショートカット

User#owner?User#has_books?(book)のようなメソッドをテストする場合を考える。

そのまま書くと以下のようになる。

subject(:user) { User.new }
describe '#owner?' do
  # ...
  it 'オーナーになる' do
    expect(user.owner?).to eq true
  end
end
describe '#has_books?' do
  # ...
  it '本を持っている' do
    expect(user.has_books?(Book.first)).to eq true
  end
end

しかし、RSpecは?メソッドに対してショートカットのマッチャーを用意しているので以下のように書ける。

subject(:user) { User.new }
describe '#owner?' do
  # ...
  it 'オーナーになる' do
    is_expected.to be_owner
  end
end
describe '#has_books?' do
  # ...
  it '本を持っている' do
    is_expected.to have_book(Book.first)
  end
end

has_book?メソッドはbe_has_bookでも良いが英語として意味が通らないのでhave_bookマッチャーが用意される(be_has_bookが使えないわけではない)。

ただまあ、上記の例の場合だとsubject { target_user.owner? }のようにしてcontexttarget_userを差し替える方が見通しが良いと思う。

kind_of?instance_of?

be_kind_ofbe_instance_ofも使えるが、be_kind_ofにはbe_a_kind_ofbe_abe_anのエイリアスが、be_instance_ofにはbe_an_instance_ofのエイリアスがある。

all

配列の全ての要素に対してマッチャーを適用する。

subject { [1, 3, 5] }

it '全て奇数' do
  subject.each do |item|
    expect(item).to be_odd
  end
end

は、以下のように書ける。

subject { [1, 3, 5] }

it '全て奇数' do
  is_expected.to all(be_odd)
end

change

メソッドの戻り値の変化を確認するマッチャー。

subject(:user) { create(:user, name: 'suzuki') }

it do
  # fromからtoに変わったことを確認
  expect { user.name = 'yamada' }.to change { user.name }.from('suzuki').to('yamada')
  # 変わったことだけを確認
  expect { user.name = 'sato' }.to change { user.name }
  # 変化量を確認
  expect { user.destroy }.to change { User.count }.by(-1)
end

contain_exactly

順序はともかく、指定した要素で構成された配列であることを確認する。

expect([:a, :b, :c]).to contain_exactly(:c, :a, :b) # OK
expect([:a, :b, :c]).to contain_exactly(:c, :b) # NG
expect([:a, :b, :c]).to contain_exactly(:c, :b, :a, :d) # NG

cover

指定した要素がRangeに含まれるか確認する。

expect(5..10).to cover(7, 8, 10)

start_with

指定した内容で始まるか確認する。

expect([2, 3, 4, 5, 10]).to start_with(2, 3, 4)
expect('abc def xyz').to start_with('abc def')

end_with

指定した内容で終わるか確認する。

expect([2, 3, 4, 5, 10]).to end_with(4, 5, 10)
expect('abc def xyz').to end_with('def xyz')

have_attributes

モデルのインスタンスを比較する際にまとめて確認する。

subject(:user) { build(:user) }

it do
  # 下記の代わりに
  # expect(user.name).to eq '...'
  # expect(user.email).to eq '...'
  # 以下のように書ける
  expect(user).to have_attributes(name: '...', email: '...')
end

include

配列、文字列、ハッシュのいずれかに対して、指定した条件にマッチするものが含まれるか確認する。

RSpec.describe Array do
  subject { [1, 3, 5] }
  it do
    is_expected.to include(1, 3)
    is_expected.to_not include(1, 7) # 7は含まれないのでnot
    is_expected.to include(be_odd)
    is_expected.to include(be <= 5)
  end
end

RSpec.describe String do
  subject { 'abc def xyz' }
  it do
    is_expected.to include('xyz')
    is_expected.to include('def', 'xy', 'a')
  end
end

RSpec.describe Hash do
  subject { { a: 1, b: 2 } }
  it do
    is_expected.to include(a: 1)
  end
end

match

文字列または正規表現に対して、指定した条件でmatch?メソッドがtrueを返すか確認する。

expect('abc').to match('b')
expect('abc').to match(/[a-z]+/)
expect(/b/).to match('abc')
expect(/[a-z]+/).to match('abc')

raise_error

例外が発生することを確認する。例外クラスかメッセージを指定することができる。

expect { 1 / 0 }.to raise_error
expect { 1 / 0 }.to raise_error(ZeroDivisionError)
expect { 1 / 0 }.to raise_error('divided by 0')
expect { 1 / 0 }.to raise_error(/0/)
expect { 1 / 0 }.to raise_error(ZeroDivisionError, 'divided by 0')

andor

マッチャーはandorで繋ぐことができる。

expect(1).to be_odd.and be_integer
expect(2).to be_odd.or eq 2
expect([0,1,2,3,4,5]).to include(be_zero.or eq 10)