代码之家  ›  专栏  ›  技术社区  ›  aarestad

Ruby rspec期望值:有什么方法可以确保关键字期望值不是“虚假的”吗?

  •  0
  • aarestad  · 技术社区  · 11 月前

    假设我有:

    class Foo
      def do_thing(arg_one:, arg_two:)
        # ...
      end
    end
    
    class Bar
      def initialize(foo)
        @foo = foo
      end
    
      def do_delegated_thing
        @foo.do_thing(arg_one: "x", arg_two: "y")
      end
    end
    

    以及测试:

    describe Bar do
      let(:mock_foo) do
        foo = double(Foo)
        allow(foo).to receive(:do_thing)
        foo
      end
    
      let(:subject) { described_class.new(mock_foo) }
    
      it "calls Foo with expected arguments" do
        subject.do_delegated_thing
    
        expect(mock_foo).to have_received(:do_thing).with(arg_one: "x", arg_two: "y")
      end
    end
    

    现在,假设我想重构 Foo 并更改参数名称:

    class Foo
      def do_thing(arg_one_hundred:, arg_two:)
        # ...
      end
    end
    

    尽管预期 receive(:do_thing).with(arg_one: "x", arg_two: "y") 现在无效。在我心中, do_thing 不应期望使用无效参数调用。是否有一种合理的方法来解决这一问题,即我应该使用更好的RSpec API方法来确保期望的合法性?

    1 回复  |  直到 11 月前
        1
  •  1
  •   engineersmnky    11 月前

    您当前发布的代码存在一些语法问题(见下文);然而,你正在寻找的是 Verifying Double 在这种情况下,a instance_double .

    来自文件(着重部分为后加):

    instance_double 是最常见的双重验证类型。它将类名或对象作为第一个参数,然后验证任何被存根的方法是否存在于 例子 那个班。此外当它接收消息时, 它验证所提供的参数是否得到方法签名的支持,包括arity和允许或必需的关键字参数(如果有的话) .

    例如,如果您更改 foo = double(Foo) foo = instance_double(Foo) 您的原始测试将通过,当您将方法更改为 def do_thing(arg_one_hundred:, arg_two:) 它将会失败。

    代码当前存在的问题:

    • Bar#initialize 不接受任何这样的论点 described_class.new(mock_foo) 将引发错误。我不确定是否应该将争论视为依赖注入,因此在这里提出正确的解决方案更加困难。
    • to_receive 不是一种方法 allow(foo).to_receive(:do_thing) 将引发错误
    • expect(...).to recieve 但是,由于您已经调用了,因此设置了对象的期望值 subject.do_delegated_thing 在设置期望之前,期望总是会失败(在方法调用之前移动期望)

    Working Example :(有明显修改)

    class Foo
      def do_thing(arg_one:, arg_two:); end
    end
    
    class Baz
      def do_thing(arg_one_hundred:, arg_two:); end
    end 
    
    class Bar
      def initialize
        @foo = Foo.new
      end
    
      def do_delegated_thing
        foo.do_thing(arg_one: "x", arg_two: "y")
      end
      private 
         attr_reader :foo
    end
    
    describe Bar do
      let(:mock_foo) {instance_double(Foo, do_thing: nil)} 
      let(:mock_baz) {double(Baz, do_thing: nil)}
      let(:mock_baz_2) {instance_double(Baz, do_thing: nil)}
    
      let(:subject) { described_class.new }
    
      it "calls Foo with expected arguments" do
        allow(subject).to receive(:foo).and_return(mock_foo)
        expect(mock_foo).to receive(:do_thing).with(arg_one: "x", arg_two: "y")
        subject.do_delegated_thing
      end
      context 'comparing double and instance_double' do 
        it "calls double with unexpected arguments" do
          allow(subject).to receive(:foo).and_return(mock_baz)
          expect(mock_baz).to receive(:do_thing).with(arg_one: "x", arg_two: "y")
          subject.do_delegated_thing
        end
        it "calls instance_double with unexpected arguments" do
          allow(subject).to receive(:foo).and_return(mock_baz_2)
          expect(mock_baz_2).to receive(:do_thing).with(arg_one: "x", arg_two: "y")
          subject.do_delegated_thing
        end
      end
    end
    

    输出:

    Bar
      calls Foo with expected arguments
      comparing double and instance_double
        calls double with unexpected arguments
        calls instance_double with unexpected arguments (FAILED - 1)
    
    Failures:
    
      1) Bar comparing double and instance_double calls instance_double with unexpected arguments
         Failure/Error: expect(mock_baz_2).to receive(:do_thing).with(arg_one: "x", arg_two: "y")
           Missing required keyword arguments: arg_one_hundred
         # ./spec.rb:42:in `block (3 levels) in <top (required)>'
         # main.rb:22:in `<main>'
    
    Finished in 0.00752 seconds (files took 0.0997 seconds to load)
    3 examples, 1 failure