ruby 用rspec只模拟嵌套块的中间层

1wnzp6jl  于 9个月前  发布在  Ruby
关注(0)|答案(1)|浏览(96)

我正在使用这个名为kubernetes_leader_election的gem编写一些代码,它在其README中有一个完整的示例来展示如何使用它。我对它的使用基本上与README中所说的相同,但这里是最相关的行:

lease_name = self.class.name.underscore.gsub(/[\/_]+/, "=")
is_leader = false
elector = KubernetesLeaderElection.new(lease_name, kubeclient, logger: Rails.logger)
Thread.new { elector.become_leader_for_life { is_leader = true } }
sleep 1 until is_leader

字符串
我试着写一个spec来测试我传入的lease_name是我期望的,并且如果块{ is_leader = true }执行,代码不会永远休眠。我知道如何mock KubernetesLeaderElection.new,但我对使用rspec mock有点陌生,所以我真的很难模仿elector.become_leader_for_life,(能够测试它传递的内容)。我已经尝试了一系列关于这个整体概念的变体,但没有一个能完全满足我的需要,而且大多数都有给予错误。这是我最新的尝试,它不会失败,但不会测试我想要的一切:

describe LeaderElectionMixin
  class MixinUser
    include LeaderElectionMixin
  end

  describe "#wait_to_be_leader" do
    let(:dummy_client) { double(Kubeclient::Client) }
    let(:dummy_elector) { double(KubernetesLeaderElection) }

    before do
      allow(Kubeclient::Client).to receive(:new).and_return(dummy_client)
      expect(dummy_elector).to receive(:become_leader_for_life) do |&block|
        # only checks that the actual block and this proc return the same thing
        expect(block).to match(Proc.new { true })
      end.and_yield
    end

    it "should create leases based on the class name" do
      expect(KubernetesLeaderElection).to receive(:new)
        .with("mixin-user", dummy_client, logger: Rails.logger)
        .and_return(dummy_elector)
      MixinUser.new.wait_to_be_leader
    end
  end
end


(EDIT更新:如果我使用.and_yield,我可以解决rspec挂起的问题,但它还不是一个完整的解决方案,因为我不能测试块内容的实际匹配,因为.and_yield使我的块不是链的末端,更多信息在这里。如果我将.and_yield移到块之前,那么expect(block)就是expect(nil)。不知道为什么。)
我的方法遇到的问题是,rspec挂起,因为is_leader = true没有执行,所以sleep 1 until is_leader将永远运行,或者(在我的最新实现中)我可以让原始块执行,但我不能Assert它确实执行了,我只能推断它确实如此,因为规范结束了。expect(block).to be或者是块代码的确切内容的字符串,或者是包含相同代码的Proc,但方法不起作用,因为实际值是Proc(所以使用字符串的建议不起作用),而且它不是同一个Proc对象(所以beeq不工作。)我也试过将.and_wrap_original附加到不同的地方,但这要么给我一个错误,要么给我和这个例子一样的行为。我能做的最好的是match,至少检查块的返回值和我做的Proc是一样的。我完全迷路了,我知道我可以切换到使用示例变量,但我不想将它暴露给包括我的mixin在内的类。
总结一下,我:
1.绝对需要最内部的块is_leader = true来实际执行,并且想测试它的内容是否是我指定的;
1.绝对需要中间的块elector.become_leader_for_life,不调用原始代码(因为它是第三方库,我不想模仿它的实现细节;)
1.不要真的关心外层Thread.new是否真的执行(规范不需要代码是多线程的,这只是为了运行时效率,因此我为我的规范模拟了sleep。)
我如何才能实现这种“只模拟中间块”的行为,而不诉诸于用模拟气味(如使用示例变量)污染代码?

i1icjdpr

i1icjdpr1#

我不知道这是干什么的:

expect(block).to be("is_leader = true")

字符串
但是代码:

expect(dummy_elector).to receive(:become_leader_for_life) do |&block|
  expect(block).to be("is_leader = true")
end


不会执行它接收到的块,所以is_leader永远不会改变。
假设你可以得到一个块的源代码,你可以做一些像这样的事情:

expect(dummy_elector).to receive(:become_leader_for_life) do |&block|
  block.call
end


可能与block.call有条件的块源?

相关问题