在某些测试中,我想设置一个引发特定异常类的模拟。因为这个特定的异常很难在测试中实例化,所以我想使用双精度。
这是一个例子。
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
消息实际上来自pry
宝石。它检查引发的错误是否通过检查exception.is_a?(Exception)
。实例双打不会对 is_a?
检查做出正确响应。
更常见的是使用参数的某些值引发错误实例:
before do
allow(some_class).to receive(:mocked_method)
.and_raise(SomeError.new(1, 'a', 'b'))
end
问题说明
TypeError
是由 Kernel#raise
引起的。
RSpec::Mocks::MessageExpectation#and_raise
将对Kernel#raise
的调用包装在Proc
中(显示在这里,稍后会调用并捕获。
Kernel#raise
接受以下内容:
$!
中引发异常,或者如果 RuntimeError
是 $!
”则引发 nil
”;或RuntimeError
,并将字符串作为消息。”;或Exception
或在发送 Exception
消息时返回 exception
对象的另一个对象”在你的情况下
instance_double(SomeError)
不是以上任何一个,因此 Kernel#raise
会抛出 TypeError
。同样的内容可以通过以下方式重现:
raise({a: 12})
in `raise': exception class/object expected (TypeError)
红鲱鱼
StandardError
起作用的原因不是你想的那样。
相反,
StandardError
只是看起来起作用,因为SomeClass#some_method
正在拯救StandardError
,而TypeError
是StandardError
(继承自StandardError
)。在这种情况下,TypeError
仍在被提升,它只是在这个过程中被拯救。
您可以通过将
and_raise(instance_double(StandardError))
更改为 and_raise(instance_double(SomeError))
(或任何其他不符合 Kernel#raise
接受的参数)的参数来证明这一点,只要 SomeClass#some_method
拯救 StandardError
或 ,代码仍然会通过TypeError
。
解决方案?
虽然我不完全理解您所面临的限制(例如“该特定异常很难在测试中实例化”),并且完全建议仅实例化
SomeError
,但您可以通过简单地创建来实现您的目标作为模拟继承自 SomeError
的 Exception。
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