RSpec:为什么 `instance_double` 可以与 StandardError 一起使用,但不能与其他异常类一起使用?

问题描述 投票:0回答:2

在某些测试中,我想设置一个引发特定异常类的模拟。因为这个特定的异常很难在测试中实例化,所以我想使用双精度。

这是一个例子。

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
时是否有一些边缘情况?或者,是否有更好的方法来模拟难以实例化的异常类?

ruby exception rspec mocking
2个回答
2
投票

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

0
投票

问题说明

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

示例

© www.soinside.com 2019 - 2024. All rights reserved.