RSpec: StandardError は動作するのに、他の例外クラスは動作しないのはなぜですか?
概要
一部のテストでは、特定の例外クラスを発生させるモックをセットアップしたいと考えています。この特定の例外をテストでインスタンス化するのは難しいため、double を使用したいと思います。
ここに例を示します。
class SomeError < StandardError
def initialize(some, random, params)
# ...
end
end
class SomeClass
def some_method
mocked_method
:ok
rescue SomeError
:ko
end
def mocked_method
true
end
end
describe SomeClass do
subject(:some_class) { described_class.new }
describe '#some_method' do
subject(:some_method) { some_class.some_method }
it { is_expected.to be :ok }
context 'when #mocked_method fails' do
before do
allow(some_class).to receive(:mocked_method)
.and_raise(instance_double(SomeError))
end
it { is_expected.to be :ko }
end
end
end
ただし、このテストの実行は次のメッセージが表示されて失敗します。
Failure/Error:
mocked_method
TypeError:
exception class/object expected
奇妙なのは、SomeError を StandardError に置き換えると、正常に動作することです。
class SomeClass
def some_method
mocked_method
:ok
rescue StandardError
:ko
end
def mocked_method
true
end
end
describe SomeClass do
subject(:some_class) { described_class.new }
describe '#some_method' do
subject(:some_method) { some_class.some_method }
it { is_expected.to be :ok }
context 'when #mocked_method fails' do
before do
allow(some_class).to receive(:mocked_method)
.and_raise(instance_double(StandardError))
end
it { is_expected.to be :ko }
end
end
end
ここで何が起きてるの? StandardError をモックするときに、特殊なケースはありますか?あるいは、インスタンス化が難しい例外クラスをモックするより良い方法はありますか?
解決策
問題の説明
TypeError は Kernel#raise によって発生します。
RSpec::Mocks::MessageExpectation#and_raise は、後で呼び出される Proc (ここに表示) で Kernel#raise への呼び出しをラップします。
Kernel#raise は以下を受け入れます。
あなたの場合、instance_double(SomeError) は上記のどれでもないため、Kernel#raise は TypeError をスローします。同じことは次のように再現できます。
raise({a: 12})
in `raise': exception class/object expected (TypeError)
レッドニシン
StandardError が機能する理由は、あなたが考えているものではありません。
代わりに、SomeClass#some_method が StandardError をレスキューし、TypeError が StandardError (StandardError から継承) であるため、StandardError は単に機能しているように見えます。この場合、TypeError は依然として発生しており、プロセス内で救済されているだけです。
これを証明するには、and_raise(instance_double(StandardError)) を and_raise(instance_double(SomeError)) (または、Kernel#raise で受け入れられる引数に従わない他の引数) に変更すると、コードは SomeClass# である限り合格します。 some_method は StandardError または TypeError をレスキューします。
解決?
あなたが直面している制限(たとえば、「その特定の例外はテストでインスタンス化するのが難しい」など)を完全には理解していませんが、SomeError をインスタンス化することだけを強くお勧めしますが、単に継承する例外を作成するだけで目的を達成できます。 SomeError をモックとして使用します。
class SomeError < StandardError
def initialize(some, random, params)
# ...
end
end
class MockSomeError < SomeError; def initialize(*);end; end
class SomeClass
def some_method
mocked_method
:ok
rescue SomeError
:ko
end
def mocked_method
true
end
end
describe SomeClass do
subject(:some_class) { described_class.new }
describe '#some_method' do
subject(:some_method) { some_class.some_method }
it { is_expected.to be :ok }
context 'when #mocked_method fails' do
before do
allow(some_class).to receive(:mocked_method)
.and_raise(MockSomeError)
end
it { is_expected.to be :ko }
end
end
end
例