RSpecメモ
shared_examples
shared_examples、shared_examples_for、shared_context
shared_examples、shared_examples_for、shared_contextはすべて同じ動きをする。
letやitなど共有したい塊がある場合に使う。
include_examples、include_context、it_behaves_like、it_should_behave_like
include_examplesとinclude_contextは動きをみる感じではほとんど同じに見えるが、実装は異なるので何かしらの違いがあるかもしれない。
it_behaves_likeとit_should_behave_likeはrspec -f docを実行した時に出力される内容が異なるだけで動きとしては同じ動きをする。
以下のようなspecがあるとする。
shared_examples 'share' do
it { expect(1).to eq 1 }
end
include_examples 'share'
it_behaves_like 'share'
これは以下のように書いたのと同じ結果になる。
# include_examples 'share'
it { expect(1).to eq 1 }
# it_behaves_like 'share'
context 'behaves like share' do
it { expect(1).to eq 1 }
end
つまり、include_examplesはそのまま呼ばれた場所にshared_examplesの内容を埋め込むが、it_behaves_likeはcontext(ないしdescribe)でくくった状態で埋め込まれる。
この違いが影響するのは、以下のようなshared_examplesを定義した場合である。
shared_examples 'share' do |param|
let(:value) { param }
it { expect(value).to eq param }
end
このshared_examplesをinclude_examplesで二回呼び出してしまうと以下のような状態になる。
# 以下のように二回`include_examples`を呼んだ場合
# include_examples 'share', 'val1'
# include_examples 'share', 'val2'
# 以下のように定義したことと同じになる
# 同じ名前のletが複数ある場合、後発の定義が有効になるので、こっちのletは無効
let(:value) { 'val1' }
it { expect(value).to eq 'val1' } # このvalueは'val2'
let(:value) { 'val2' }
it { expect(value).to eq 'val2' }
フック
before、after、aroundのフックがある。また引数として:each(:exampleでも同じ)、:all(:contextでも同じ)を受け取る。デフォルトの指定は:exampleでitごとにフックが実行される。:all(:context)を指定するとフックの記述されているdescribeかcontextごとに一回だけフックが実行される。
aroundは:each(:example)しか受け取れないので引数を指定する必要はないはず。
around { |e| puts 'around default'; e.run }
around(:each) { |e| puts 'around each'; e.run }
around(:example) { |e| puts 'around example'; e.run }
before { puts 'before default' }
before(:each) { puts 'before each' }
before(:example) { puts 'before example' }
before(:context) { puts 'before context' }
before(:all) { puts 'before all' }
after { puts 'after default' }
after(:each) { puts 'after each' }
after(:example) { puts 'after example' }
after(:context) { puts 'after context' }
after(:all) { puts 'after all' }
describe do
it { expect(1).to eq 1 }
it { expect(1).to eq 1 }
end
describe do
it { expect(1).to eq 1 }
end
を実行すると以下の通りになる。
before context
before all
around default
around each
around example
before default
before each
before example
after example
after each
after default
around default
around each
around example
before default
before each
before example
after example
after each
after default
around default
around each
around example
before default
before each
before example
after example
after each
after default
after all
after context
また、aroundでインスタンス変数を定義してもspec内から参照できない。そもそも、 インスタンス変数を定義せずにletを使うべきである。
let
メモ化したメソッドの定義をする。let(:user) { create(:user) }とした場合、userが参照された時に1度だけcreate(:user)が実行される。let!を使うとbeforeフックと同タイミング(おそらくbeforeより前)に1度だけ実行される。
subject
デフォルトではdescribed_classと一致する。letと同じような形で定義でき、it内ではsubjectで参照できる。
RSpec.describe User, type: :model do
describe '#name' do
# 以下3つのitは等価
it { expect(User.name).to eq 'name' }
it { expect(described_class.name).to eq 'name' }
it { expect(subject.name).to eq 'name' }
end
end
expect(subject)の代わりにis_expectedとすることができる。また引数にシンボルを渡すとletで定義したのと同じようにその名前で参照できる。
RSpec.describe User, type: :model do
describe '#name' do
# 引数は省略できる
subject(:name) { User.name }
# 以下5つのitは等価
it { expect(User.name).to eq 'name' }
it { expect(described_class.name).to eq 'name' }
it { expect(subject).to eq 'name' }
it { expect(name).to eq 'name' }
it { is_expected.to eq 'name' }
end
end
let!と同じようにsubject!もある。
ヘルパーメソッド
Rubyのメソッド定義を使ってヘルパーメソッドを定義することができる。ヘルパーメソッドは定義したdescribeやcontextの中でのみ有効になる。
RSpec.describe User, type: :model do
def helper(arg)
expect(arg).to eq 1
end
it { helper(1) }
end