ruby RSpec:为什么`instance_double`可以处理StandardError,而不能处理其他异常类?

1dkrff03  于 2023-10-18  发布在  Ruby
关注(0)|答案(2)|浏览(98)

在一些测试中,我想设置一个引发特定异常类的mock。因为这个特殊的异常很难在测试中示例化,所以我想使用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

奇怪的是,如果我用StandardError替换SomeError,它工作得很好。

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

siv3szwd

siv3szwd1#

问题说明

TypeErrorKernel#raise引起。
RSpec::Mocks::MessageExpectation#and_raise将对Kernel#raise的调用 Package 在稍后调用的Proc(如图所示)中。
Kernel#raise接受以下内容:

  • 无参数- “在$!中引发异常,或者如果$!nil则引发RuntimeError;或
  • 一个字符串- “引发一个RuntimeError,字符串作为一个消息。";或
  • “* 一个Exception或另一个对象,当发送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,而TypeErrorStandardError(继承自StandardError)。在这种情况下,TypeError仍在上升,它只是在这个过程中被拯救。
您可以通过将and_raise(instance_double(StandardError))更改为and_raise(instance_double(SomeError))(或任何其他不符合Kernel#raise接受的参数的参数)来证明这一点,只要SomeClass#some_method拯救StandardErrorTypeError,代码仍然会通过。

解决方案?

虽然我不完全理解你所受到的限制(例如:* “那个特殊的异常很难在测试中示例化”*),并且完全建议只示例化一个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

例如

ia2d9nvy

ia2d9nvy2#

TypeError消息实际上来自pry gem。它检查引发的错误是否通过了exception.is_a?(Exception)检查。示例double不会对is_a?检查响应true。
更常见的方法是使用参数的某些值引发错误的示例:

before do
  allow(some_class).to receive(:mocked_method)
    .and_raise(SomeError.new(1, 'a', 'b'))
end

相关问题