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