任意のインスタンス
rspec-mocks は、allow_any_instance_of
と expect_any_instance_of
の2つのメソッドを提供しています。これらのメソッドを使用すると、クラスの任意のインスタンスをスタブ化またはモック化することができます。これらのメソッドは、allow
や expect
の代わりに使用されます。
allow_any_instance_of(Widget).to receive(:name).and_return("Wibble")
expect_any_instance_of(Widget).to receive(:name).and_return("Wobble")
これらのメソッドは、Widget
のすべてのインスタンスに対して適切なスタブまたは期待を追加します。
同様に、応答を設定することもできます。
この機能は、レガシーコードとの作業時に便利な場合もありますが、一般的には以下の理由から使用を推奨していません:
rspec-mocks
の API は個々のオブジェクトインスタンスを対象として設計されていますが、この機能はオブジェクトクラス全体に対して操作を行います。そのため、意味的に混乱するエッジケースがいくつか存在します。たとえば、expect_any_instance_of(Widget).to receive(:name).twice
の場合、特定のインスタンスがname
を2回受け取ることが期待されているのか、合計で2回の受信が期待されているのかは明確ではありません。(前者です。)- この機能の使用は、しばしば設計上の問題を示しています。テストが行おうとしていることが多すぎる場合や、テスト対象のオブジェクトが複雑すぎる場合があります。
- これは
rspec-mocks
の中で最も複雑な機能であり、過去に最も多くのバグ報告を受けています。(コアチームの誰もが積極的に使用していないため、問題が解決されにくいです。)
allow_any_instance_of
を使用してメソッドをスタブ化する
次の内容で "example_spec.rb" という名前のファイルがあるとします:
RSpec.describe "allow_any_instance_of" do
it "returns the specified value on any instance of the class" do
allow_any_instance_of(Object).to receive(:foo).and_return(:return_value)
o = Object.new
expect(o.foo).to eq(:return_value)
end
end
rspec example_spec.rb
を実行すると、
すべての例がパスするはずです。
allow_any_instance_of
を使用して複数のメソッドをスタブ化する
次の内容で "example_spec.rb" という名前のファイルがあるとします:
RSpec.describe "allow_any_instance_of" do
context "with receive_messages" do
it "stubs multiple methods" do
allow_any_instance_of(Object).to receive_messages(:foo => 'foo', :bar => 'bar')
o = Object.new
expect(o.foo).to eq('foo')
expect(o.bar).to eq('bar')
end
end
end
When I run rspec example_spec.rb
Then the examples should all pass.
特定の引数を持つクラスの任意のインスタンスをスタブ化する
ファイル名が「example_spec.rb」であるという条件で、以下のコードが記述されているとします。
RSpec.describe "allow_any_instance_of" do
context "with arguments" do
it "returns the stubbed value when arguments match" do
allow_any_instance_of(Object).to receive(:foo).with(:param_one, :param_two).and_return(:result_one)
allow_any_instance_of(Object).to receive(:foo).with(:param_three, :param_four).and_return(:result_two)
o = Object.new
expect(o.foo(:param_one, :param_two)).to eq(:result_one)
expect(o.foo(:param_three, :param_four)).to eq(:result_two)
end
end
end
When I run rspec example_spec.rb
Then the examples should all pass.
ブロック実装は最初の引数としてレシーバを受け取る
ファイル名が「example_spec.rb」であるという条件で、以下のコードが記述されているとします。
RSpec.describe "allow_any_instance_of" do
it 'yields the receiver to the block implementation' do
allow_any_instance_of(String).to receive(:slice) do |instance, start, length|
instance[start, length]
end
expect('string'.slice(2, 3)).to eq('rin')
end
end
When I run rspec example_spec.rb
Then the examples should all pass.
expect_any_instance_of
を使用して任意のインスタンスにメッセージの期待値を設定する
ファイル名が「example_spec.rb」であるという条件で、以下のコードが記述されているとします。
RSpec.describe "expect_any_instance_of" do
before do
expect_any_instance_of(Object).to receive(:foo)
end
it "passes when an instance receives the message" do
Object.new.foo
end
it "fails when no instance receives the message" do
Object.new.to_s
end
end
When I run rspec example_spec.rb
Then it should fail with the following output:
| 2 examples, 1 failure | | Exactly one instance should have received the following message(s) but didn't: foo |
複数の呼び出しに対して異なる戻り値を設定する(allow_any_instance_of との組み合わせ)
allow_any_instance_of
を使用して複数の呼び出しを行う場合、各インスタンスは指定された順序で設定された戻り値を返し、最後の値を繰り返し返す動作となります。
ファイル名が「multiple_calls_spec_with_allow_any_instance_of.rb」であるという条件で、以下のコードが記述されているとします。
class SomeClass
end
RSpec.describe "When the method is called multiple times on different instances with allow_any_instance_of" do
it "demonstrates the mocked behavior on each instance individually" do
allow_any_instance_of(SomeClass).to receive(:foo).and_return(1, 2, 3)
first = SomeClass.new
second = SomeClass.new
third = SomeClass.new
expect(first.foo).to eq(1)
expect(second.foo).to eq(1)
expect(first.foo).to eq(2)
expect(second.foo).to eq(2)
expect(first.foo).to eq(3)
expect(first.foo).to eq(3) # repeats last value from here
expect(second.foo).to eq(3)
expect(second.foo).to eq(3) # repeats last value from here
expect(third.foo).to eq(1)
expect(third.foo).to eq(2)
expect(third.foo).to eq(3)
expect(third.foo).to eq(3) # repeats last value from here
end
end
When I run rspec multiple_calls_spec_with_allow_any_instance_of.rb
Then the examples should all pass.