メインコンテンツまでスキップ

共有例の使用方法

共有例を使用すると、クラスやモジュールの振る舞いを記述することができます。共有例が宣言されると、共有グループの内容が保存されます。共有グループが実行される際には、別の例のグループのコンテキストが提供されます。

共有グループは、次のいずれかの方法で別のグループに含めることができます。

(include_examples "name"      # include the examples in the current context
it_behaves_like "name" # include the examples in a nested context
it_should_behave_like "name" # include the examples in a nested context
matching metadata # include the examples in the current context

注意: 共有グループを含むファイルは、それを使用するファイルよりも先に読み込まれる必要があります。これに対処するための規約はありますが、RSpecは特別な処理(自動読み込みなど)を行いません。それを行うには、既存のスイートを壊す可能性のあるファイルの厳格な命名規則が必要です。

注意: 現在のコンテキストでパラメータ化された例を複数回含める場合、前のメソッド定義が上書きされ、最後の宣言が有効になります。したがって、次のような共有例(または共有コンテキスト)がある場合、

(RSpec.shared_examples "some example" do |parameter|
\# Same behavior is triggered also with either `def something; 'some value'; end`
\# or `define_method(:something) { 'some value' }`
let(:something) { parameter }
it "uses the given parameter" do
expect(something).to eq(parameter)
end
end

RSpec.describe SomeClass do
include_examples "some example", "parameter1"
include_examples "some example", "parameter2"
end

実際には次のようになります(最初の例が失敗することに注意してください):

(RSpec.describe SomeClass do
\# Reordered code for better understanding of what is happening
let(:something) { "parameter1" }
let(:something) { "parameter2" }

it "uses the given parameter" do
\# This example will fail because last let "wins"
expect(something).to eq("parameter1")
end

it "uses the given parameter" do
expect(something).to eq("parameter2")
end
end

このような微妙なエラーを防ぐために、同じコンテキスト内で同じ名前のメソッドを複数宣言すると警告が表示されます。この警告が表示された場合、最も簡単な解決策は、include_examplesit_behaves_likeに置き換えることです。これにより、it_behaves_likeによって作成されるネストされたコンテキストによってメソッドの上書きが回避されます。

規約:

  1. 最も簡単なアプローチは、共有例を使用するファイルから明示的に共有例を含むファイルを要求することです。RSpecはspecディレクトリをLOAD_PATHに追加するため、require 'shared_examples_for_widgets'と記述することで、#{PROJECT_ROOT}/spec/shared_examples_for_widgets.rbにあるファイルを要求することができます。

  2. 一つの規約は、共有例を含むファイルをspec/support/に配置し、そのディレクトリ内のファイルをspec/spec_helper.rbから要求することです。

Dir["./spec/support/**/*.rb"].sort.each { |f| require f }

これは、rspec-railsで生成されるspec/spec_helper.rbファイルに含まれていましたが、テストスイートの起動時間を短く保つために、このようなディレクトリ内のすべてのファイルを自動的に読み込まない方が良いアイデアです。1つのスペックファイルのみを実行する場合、不要な依存関係の読み込みや不要なセットアップは、最初の例が実行されるまでにかかる時間に対して、かなりの、目に見える効果をもたらすことがあります。

  1. 共有グループが含まれるすべてのグループが同じファイルに存在する場合は、そのファイル内で共有グループを宣言します。

同じファイル内の2つのグループに含まれる共有例グループ

Given ファイル名が "collection_spec.rb" のファイルがあるとき、以下の内容を含む:

require "set"

RSpec.shared_examples "a collection" do
let(:collection) { described_class.new([7, 2, 4]) }

context "initialized with 3 items" do
it "says it has three items" do
expect(collection.size).to eq(3)
end
end

describe "#include?" do
context "with an item that is in the collection" do
it "returns true" do
expect(collection.include?(7)).to be(true)
end
end

context "with an item that is not in the collection" do
it "returns false" do
expect(collection.include?(9)).to be(false)
end
end
end
end

RSpec.describe Array do
it_behaves_like "a collection"
end

RSpec.describe Set do
it_behaves_like "a collection"
end

When rspec collection_spec.rb --format documentation を実行すると、

Then すべての例がパスするべきです

And 出力には以下が含まれるべきです:

Array
behaves like a collection
initialized with 3 items
says it has three items
#include?
with an item that is in the collection
returns true
with an item that is not in the collection
returns false

Set
behaves like a collection
initialized with 3 items
says it has three items
#include?
with an item that is in the collection
returns true
with an item that is not in the collection
returns false

ブロックを使用して共有グループにコンテキストを提供する

Given ファイル名が "shared_example_group_spec.rb" のファイルがあるとき、以下の内容を含む:

require "set"

RSpec.shared_examples "a collection object" do
describe "<<" do
it "adds objects to the end of the collection" do
collection << 1
collection << 2
expect(collection.to_a).to match_array([1, 2])
end
end
end

RSpec.describe Array do
it_behaves_like "a collection object" do
let(:collection) { Array.new }
end
end

RSpec.describe Set do
it_behaves_like "a collection object" do
let(:collection) { Set.new }
end
end

When rspec shared_example_group_spec.rb --format documentation を実行すると、

Then すべての例がパスするべきです

And 出力には以下が含まれるべきです:

Array
behaves like a collection object
<<
adds objects to the end of the collection

Set
behaves like a collection object
<<
adds objects to the end of the collection

共有例グループにパラメータを渡す

Given ファイル名が "shared_example_group_params_spec.rb" のファイルがあるとき、以下の内容を含む:

RSpec.shared_examples "a measurable object" do |measurement, measurement_methods|
measurement_methods.each do |measurement_method|
it "should return #{measurement} from ##{measurement_method}" do
expect(subject.send(measurement_method)).to eq(measurement)
end
end
end

RSpec.describe Array, "with 3 items" do
subject { [1, 2, 3] }
it_should_behave_like "a measurable object", 3, [:size, :length]
end

RSpec.describe String, "of 6 characters" do
subject { "FooBar" }
it_should_behave_like "a measurable object", 6, [:size, :length]
end

When rspec shared_example_group_params_spec.rb --format documentation を実行すると、

Then すべての例がパスするべきです

And 出力には以下が含まれるべきです:

Array with 3 items
it should behave like a measurable object
should return 3 from #size
should return 3 from #length

String of 6 characters
it should behave like a measurable object
should return 6 from #size
should return 6 from #length

it_should_behave_likeit_has_behavior にエイリアスする

Given ファイル名が "shared_example_group_spec.rb" のファイルがあるとき、以下の内容を含む:

RSpec.configure do |c|
c.alias_it_should_behave_like_to :it_has_behavior, 'has behavior:'
end

RSpec.shared_examples 'sortability' do
it 'responds to <=>' do
expect(sortable).to respond_to(:<=>)
end
end

RSpec.describe String do
it_has_behavior 'sortability' do
let(:sortable) { 'sample string' }
end
end

When rspec shared_example_group_spec.rb --format documentation を実行すると、

Then すべての例がパスするべきです

And 出力には以下が含まれるべきです:

String
has behavior: sortability
responds to <=>

共有メタデータを自動的に含むと共有例グループが含まれます

Given ファイル名が "shared_example_metadata_spec.rb" のファイルがあるとき、以下の内容を含む:

RSpec.shared_examples "shared stuff", :a => :b do
it 'runs wherever the metadata is shared' do
end
end

RSpec.describe String, :a => :b do
end

When rspec shared_example_metadata_spec.rb を実行すると、

Then 出力には以下が含まれるべきです:

1 例, 0 失敗

共有例はコンテキストによってネストできます

Given ファイル名が "context_specific_examples_spec.rb" のファイルがあるとき、以下の内容を含む:

RSpec.describe "shared examples" do
context "per context" do

shared_examples "shared examples are nestable" do
specify { expect(true).to eq true }
end

it_behaves_like "shared examples are nestable"
end
end

When rspec context_specific_examples_spec.rb を実行すると、

Then 出力には以下が含まれるべきです:

1 例, 0 失敗

共有の例は子コンテキストからアクセス可能です

「context_specific_examples_spec.rb」という名前のファイルがあるとします。

RSpec.describe "shared examples" do
shared_examples "shared examples are nestable" do
specify { expect(true).to eq true }
end

context "per context" do
it_behaves_like "shared examples are nestable"
end
end

rspec context_specific_examples_spec.rb を実行すると、

出力には次の内容が含まれているはずです。

1 example, 0 failures

また、出力には次の内容が含まれていないはずです。

Accessing shared_examples defined across contexts is deprecated

共有の例はコンテキストごとに分離されます

「isolated_shared_examples_spec.rb」という名前のファイルがあるとします。

RSpec.describe "shared examples" do
context do
shared_examples "shared examples are isolated" do
specify { expect(true).to eq true }
end
end

context do
it_behaves_like "shared examples are isolated"
end
end

rspec isolated_shared_examples_spec.rb を実行すると、

出力には次の内容が含まれているはずです。

Could not find shared examples "shared examples are isolated"