如何在Rspec 3.5中测试模块中的内核休眠方法?

时间:2017-01-22 21:38:53

标签: ruby-on-rails ruby rspec kernel

我在模块的私有部分有以下方法......

  def add_api_delay
    sleep(retry_delay * (retry_multiplier_adjustment - retries)) if retries.positive?
  end

我正在使用的规范到目前为止看起来像这样......

  let!(:klass) do
    Class.new do
      include AmazonMws::Shared::Utilities
      attr_accessor :retries, :retry_multiplier_adjustment, :retry_delay
      def initialize
        @retries = 1
        @retry_delay = 1
        @retry_multiplier_adjustment = 2
      end

      def test_add_api_delay
        add_api_delay
      end
    end
  end

  describe '.add_api_delay', focus: true do
    # let(:kernel_spy) { class_spy(Kernel, sleep: true) }

    before do

    end

    it 'sleeps retry api calls' do
      # allow(klass).to receive(:sleep).with(1).and_return(kernel_spy)
      # expect(kernel_spy).to have_received(:sleep)
      # expect(Kernel).to receive(:sleep).with(1)
      expect(Kernel).to receive(:sleep).and_return(true)
      klass.new.test_add_api_delay
    end
  end

我有一些目标和原因我想测试这个私有方法,但是如何调用sleep sleep。我不想放慢套件的速度,所以理想情况下我试图使用类间谍来对抗内核。我测试的任何东西似乎都没有用。

更新

  describe '.add_api_delay' do
    before do
      allow_any_instance_of(klass).to receive(:sleep).and_return(1)
    end

    it 'sleeps retry api calls' do
      expect(klass.new.test_add_api_delay).to eq(1)
    end
  end

然而,这是有效的,因为它标记了下面的警察......

C: RSpec/AnyInstance: Avoid stubbing using allow_any_instance_of
      allow_any_instance_of(klass).to receive(:sleep).and_return(1)

你有什么想法?

3 个答案:

答案 0 :(得分:2)

我认为它不起作用,因为你没有把期望放在正确的事情上。看起来你已经尝试将它放在Kernel和被测试的类上,但你需要把它放在实例上:

it 'sleeps retry api calls' do
  thing = klass.new
  allow(thing).to receive(:sleep)

  thing.test_add_api_delay

  expect(thing).to have_received(:sleep).with(1)
end

上面有一种测试气味,因为它正在测试被测试的课程。但我认为这可能比在这里强制执行一些设计约束更好,并且在调用sleep时失去了一些Ruby的优雅。

答案 1 :(得分:0)

您应该使用allow_any_instace_of

RSpec.describe "allow_any_instance_of" do
  it "returns the specified value on any instance of the class" do
    allow_any_instance_of(Object).to receive(:foo).and_return(:return_value)

    o = Object.new
    expect(o.foo).to eq(:return_value)
  end
end

答案 2 :(得分:0)

以下是调用sleep的私有方法的RSpec测试的最小示例。

class Sleeper
  def initialize(delay:)
    @delay = delay
  end
  attr_reader :delay

  private

  def rest
    sleep delay
  end
end

require 'rspec'

RSpec.describe Sleeper do
  let(:sleeper) { Sleeper.new(delay: delay) }
  let(:delay) { 10 }

  # Comment here explaining why this test is necessary
  describe '.send :rest' do
    before { allow_any_instance_of(Sleeper).to receive(:sleep) }

     it 'sleeps' do
       expect(sleeper).to receive(:sleep)
       sleeper.send :rest
     end
  end
end

更新:

你必须在这里做出决定。

Rubocop对“避免存根”的建议是一个很好的建议,但你说你有理由测试私有方法是否使用内核方法,并且存根是最好的方法。如果测试的原因比Rubocop纯度更重要,那么你应该忽略Rubocop警告。

如果Rubocop的建议更重要,那么我建议编写一个计算延迟的公共方法,为此编写测试。也许是这样的:

def retry_delay_duration
  return 0 if retries < 1
  retry_delay * (retry_multiplier_adjustment - retries)
end

private

def add_api_delay
  sleep retry_delay_duration
end

在第二种情况下,您应该删除add_api_delay的测试,并且仅测试公共方法retry_delay_duration(或任何您称之为的方法)返回正确的延迟。