期望模拟结果接收方法

Expect mock result to receive method

我正在尝试模拟一个 class,这样我就可以期望它被实例化,然后调用某个方法。

我试过了:

    expect(MyPolicy).
      to receive(:new).
      and_wrap_original do |method, *args|
        expect(method.call(*args)).to receive(:show?).and_call_original
      end

但我得到的只是:

undefined method `show?' for #RSpec::Mocks::VerifyingMessageExpectation:0x0055e9ffd0b530

我试过提供一个块并首先调用原始方法(:new 和 :show?,我必须先绑定),但错误总是一样。

我知道 expect_any_instance_of,但它被认为是代码异味,所以我正在尝试找到另一种正确的方法。

上下文:我有专家政策,我想检查它是否已被调用

我也试过了,同样的错误:

    ctor = policy_class.method(:new)

    expect(policy_class).
      to receive(:new).
      with(user, record) do
        expect(ctor.call(user, record)).to receive(query).and_call_original
      end

你破产了MyPolicy.new

您的 new 包装器没有 return 新的 MyPolicy 对象。它 return 是 expect(method.call(*args)).to receive(:show?).and_call_original 的结果,它是一个 MessageExpectation。

相反,您可以确保新对象 return 使用 tap 编辑。

      # This is an allow. It's not a test, it's scaffolding for the test.
      allow(MyPolicy).to receive(:new)
        .and_wrap_original do |method, *args|
          method.call(*args).tap do |obj|
            expect(obj).to receive(:show?).and_call_original
          end
        end

或者用老式的方法。

      allow(MyPolicy).to receive(:new)
        .and_wrap_original do |method, *args|
          obj = method.call(*args)
          expect(obj).to receive(:show?).and_call_original
          obj
        end

将这两个步骤分开通常会更简单。将 MyPolicy.new 模拟到 return 特定对象,然后期望调用显示?在那个对象上。

let(:policy) do
  # This calls the real MyPolicy.new because policy is referenced
  # when setting up the MyPolicy.new mock.
  MyPolicy.new
end

before do
  allow(MyPolicy).to receive(:new).and_return(policy)
end
    
it 'shows' do
  expect(policy).to receive(:show?).and_call_original
  MyPolicy.new.show?
end

这确实意味着 MyPolicy.new 总是 return 同一个对象。这是测试的优势,但可能会破坏某些东西。这更加灵活,因为它将脚手架与正在测试的内容分开。脚手架可以重复使用

RSpec.describe SomeClass do
  let(:policy) {
    MyPolicy.new
  }
  let(:thing) {
    described_class.new
  }

  shared_context 'mocked MyPolicy.new' do
    before do
      allow(MyPolicy).to receive(:new).and_return(policy)
    end
  end
  
  describe '#some_method' do
    include_context 'mocked new'
    
    it 'shows a policy' do
      expect(policy).to receive(:show?).and_call_original

      thing.some_method
    end
  end
  
  describe '#other_method' do
    include_context 'mocked MyPolicy.new'
    
    it 'checks its policy' do
      expect(policy).to receive(:check)

      thing.other_method
    end
  end
end

最后,不可访问的构造函数调用对于测试来说是一个令人头疼的问题,而且它们不灵活。这是默认值,无法覆盖。

class SomeClass
  def some_method
    MyPolicy.new.show?
  end  
end

将其转换为具有默认值的访问器。

class SomeClass
  attr_writer :policy
  
  def policy
    @policy ||= MyPolicy.new
  end
  
  def some_method
    policy.show?
  end  
end

现在可以在测试中或其他任何地方访问。

RSpec.describe SomeClass do
  let(:thing) {
    described_class.new
  }
  
  describe '#some_method' do    
    it 'shows its policy' do
      expect(thing.policy).to receive(:show?).and_call_original
      thing.some_method
    end
  end
end

这是最稳健的选择。