Rspecでテストコードを書くとやたらめったらコードが長くなることがありますよね、ここではShared exampleを利用してコードを共有する(DRY)方法を紹介します。
※RSpecのバージョンはRspec3を想定しています。

例えばこんなテストコードがあるとします。


describe 'Unshared' do
  let(:b){ true }
  let(:i){ 1 }
  let(:s){ 'hello' }
  context 'in context 1' do
    it "is true" do
      expect(b).to eq(true)
    end
    it "is 1" do
      expect(i).to eq(1)
    end
    it "is hello" do
      expect(s).to eq('hello')
    end
  end
  context 'in context 2' do
    it "is true" do
      expect(b).to eq(true)
    end
    it "is 1" do
      expect(i).to eq(1)
    end
    it "is hello" do
      expect(s).to eq('hello')
    end
  end
end

expect…箇所が完全にコピペですね、このような箇所はShared Exampleで一箇所にまとめることができます。
shared_examplesに共有化するテストコードを書いてit_behaves_likeで呼び出します。


describe 'SharedExamples' do
  shared_examples "share me" do
    it "is true" do
      expect(b).to eq(true)
    end
    it "is 1" do
      expect(i).to eq(1)
    end
    it "is hello" do
      expect(s).to eq('hello')
    end
  end
  let(:b){ true }
  let(:i){ 1 }
  let(:s){ 'hello' }
  context 'in context 1' do
    it_behaves_like "share me"
  end
  context 'in context 2' do
    it_behaves_like "share me"
  end
end

Shared Exampleには変数やブロックの引数を渡すことができます。

変数を引数にするには”it_behaves_like <名前>”の後にカンマ区切りで指定します。


describe 'SharedExamples Args' do
  shared_examples "share me" do |arg_b, arg_i, arg_s|
    it "is true" do
      expect(arg_b).to eq(true)
    end
    it "is 1" do
      expect(arg_i).to eq(1)
    end
    it "is hello" do
      expect(arg_s).to eq('hello')
    end
  end
  context 'in context 1' do
    it_behaves_like "share me", true, 1, 'hello'
  end
  context 'in context 2' do
    it_behaves_like "share me", true, 1, 'hello'
  end
end

“it_behaves_like <名前>”の後にdo-endブロックを指定すると、Shared Exampleにブロック引数が渡ります。
以下の例ではブロック引数内で変数の定義をしています。


describe 'SharedExamples Block' do
  shared_examples "share me" do
    it "is true" do
      expect(b).to eq(true)
    end
    it "is 1" do
      expect(i).to eq(1)
    end
    it "is hello" do
      expect(s).to eq('hello')
    end
  end
  context 'in context 1' do
    it_behaves_like "share me" do
      let(:b){ true }
      let(:i){ 1 }
      let(:s){ 'hello' }
    end
  end
end