1

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

我试过了:

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

但我得到的只是:

未定义的方法“显示?” 对于#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
4

1 回答 1

2

你打破了MyPolicy.new

您的包装器new不会返回新的 MyPolicy 对象。它返回的结果expect(method.call(*args)).to receive(:show?).and_call_original是 MessageExpectation。

相反,您可以确保新对象以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 以返回特定对象,然后期望调用显示?在那个物体上。

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 总是返回相同的对象。这是测试的优势,但可能会破坏某些东西。这更灵活,因为它将脚手架与正在测试的东西分开。脚手架可以重复使用。

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

这是最稳健的选择。

于 2021-03-31T19:20:35.840 回答