1

也许,会有太多的代码,但我想了解为什么 RSpec 以这种方式工作。我正在尝试double与远程 REST API 交互的外部对象。

示例代码:

class App

  class << self
    def foo
      external.func1
    end

    def bar
      external.func2
    end

    def external
      @external ||= ExternalObject.new
    end
  end

end

class ExternalObject
  def func1
    puts 'func1'
  end

  def func2
    puts 'func2'
  end
end

该规范有效:

require 'rspec'
require_relative 'app'

describe App do

  describe '.foo' do
    it 'calls func1' do
      expect_any_instance_of(ExternalObject).to receive(:func1)

      described_class.foo
    end
  end

  describe '.bar' do
    it 'calls func2' do
      expect_any_instance_of(ExternalObject).to receive(:func2)

      described_class.bar
    end
  end

end

但这不起作用:

require 'rspec'
require_relative 'app'

describe App do

  let(:external) { double('ExternalObject', func1: 1, func2: 2) }
  before(:each) do
    allow(ExternalObject).to receive(:new).and_return(external)
  end

  describe '.foo' do
    it 'calls func1' do
      expect(external).to receive(:func1)

      described_class.foo
    end
  end

  describe '.bar' do
    it 'calls func2' do
      expect(external).to receive(:func2)

      described_class.bar
    end
  end

end

执行第二个示例时,RSpec 输出看起来double不再包含存根:

1) App.bar calls func2
     Failure/Error: described_class.bar
       Double "ExternalObject" received unexpected message :func2 with (no args)

如果将代码替换App.external为此:

@external = ExternalObject.new

一切正常。

我搜索了该错误的原因,但没有找到任何东西。

更新

此外,为了防止类的记忆,可以克隆对象。也许这不是一个好主意,但它确实有效。

require 'rspec'
require_relative 'app'

describe App do

  let(:external) { double('ExternalObject', func1: 1, func2: 2) }
  before(:each) do
    stub_const('App', App.clone)
    allow(ExternalObject).to receive(:new).and_return(external)
  end

  describe '.foo' do
    it 'calls func1' do
      expect(external).to receive(:func1)

      App.foo
    end
  end

  describe '.bar' do
    it 'calls func2' do
      expect(external).to receive(:func2)

      App.bar
    end
  end

end

更新 2

我将我的问题具体化:为什么double@external测试之间进行记忆,但没有存根方法?

4

1 回答 1

0

因为let为每个示例创建一个新的双精度。

尝试使用最新的 (3.6) rspec 运行此规范,您将收到以下错误:

1) App.bar calls func2
   Failure/Error: external.func2
     #<Double "ExternalObject"> was originally created in one example but has leaked into another example and can no longer be used. rspec-mocks' doubles are designed to only last for one example, and you need to create a new one in each example you wish to use it for.

这是因为对象在类级别上的记忆。

当第一个示例运行时,let创建第一个具有第一个期望的双精度并被记忆。

在下一个示例中,let创建了一个新的双精度并在该新双精度上设置了期望值,但是当测试代码调用described_class.external它时,它会收到第一个记忆的测试双精度值,而不是具有新期望值的新值。

您可以尝试在以下示例中查看:

RSpec.describe App do
  let(:external) { double('ExternalObject', func1: 1, func2: 2) }

  before { allow(ExternalObject).to receive(:new).and_return(external) }

  describe '.foo' do
    it 'calls func1' do
      p external.object_id # => 70184943614340
      p described_class.external.object_id # => 70184943614340
      expect(external).to receive(:func1)

      described_class.foo
    end
  end

  describe '.bar' do
    it 'calls func2' do
      p external.object_id # => 70184942937140
      p described_class.external.object_id # => 70184943614340
      expect(external).to receive(:func2)

      described_class.bar
    end
  end
end
于 2017-06-05T06:05:51.750 回答