RSpecメモ

shared_examples

shared_examplesshared_examples_forshared_context

shared_examplesshared_examples_forshared_contextはすべて同じ動きをする。

letitなど共有したい塊がある場合に使う。

include_examplesinclude_contextit_behaves_likeit_should_behave_like

include_examplesinclude_contextは動きをみる感じではほとんど同じに見えるが、実装は異なるので何かしらの違いがあるかもしれない。 it_behaves_likeit_should_behave_likerspec -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_likecontext(ないしdescribe)でくくった状態で埋め込まれる。

この違いが影響するのは、以下のようなshared_examplesを定義した場合である。

shared_examples 'share' do |param|
  let(:value) { param }
  it { expect(value).to eq param }
end

このshared_examplesinclude_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' }

フック

beforeafteraroundのフックがある。また引数として:each:exampleでも同じ)、:all:contextでも同じ)を受け取る。デフォルトの指定は:exampleitごとにフックが実行される。:all:context)を指定するとフックの記述されているdescribecontextごとに一回だけフックが実行される。

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のメソッド定義を使ってヘルパーメソッドを定義することができる。ヘルパーメソッドは定義したdescribecontextの中でのみ有効になる。

RSpec.describe User, type: :model do
  def helper(arg)
    expect(arg).to eq 1
  end

  it { helper(1) }
end