【问题标题】:How can I mock super in ruby using rspec?如何使用 rspec 在 ruby​​ 中模拟 super?
【发布时间】:2013-04-06 21:39:20
【问题描述】:

我正在通过创建一个扩展到库类的子类来扩展现有库。

在子类中,我能够测试initialize 方法中的大部分功能,但无法模拟super 调用。子类如下所示。

class Child < SomeLibrary
    def initialize(arg)
        validate_arg(arg)
        do_something
        super(arg)
    end

    def validate_arg(arg)
        # do the validation
    end

    def do_something
        @setup = true
    end
end

如何编写 rspec 测试(使用 mocha)以便可以模拟 super 调用?请注意,我正在 Child 类中测试 initialize 方法的功能。当提供额外参数时,我是否必须创建不调用super 的单独代码路径?

【问题讨论】:

    标签: ruby rspec rspec2


    【解决方案1】:

    你不能模拟super,你也不应该。当您模拟某些内容时,您正在验证是否收到了特定消息,而super 不是消息,而是关键字。

    相反,如果缺少super 调用,请弄清楚此类的哪些行为会发生变化,并编写一个示例来练习和验证该行为。

    【讨论】:

    • 这是否意味着我不能为初始化编写 rspec 测试?为初始化方法编写单元测试是一种不好的做法吗?顺便说一句,我正在扩展数据库连接器库。如果我不写单元测试,而是写集成测试,我只是觉得如果 rspec 测试需要连接到真实数据库,这很奇怪。
    • 为什么我们不能模拟 super 对我来说没有意义。这样做似乎对单独测试初始化​​行为很有用,而且 super 肯定是发送到类实例的消息,不是吗?
    • 不,super不是发送到类实例的消息。我知道没有 ruby​​ 元编程结构可以使这成为可能,即使是这样,我仍然不相信这是一个好主意。
    • @MyronMarston 在某些情况下您需要 Stub,例如在 mixin 中...
    【解决方案2】:

    测试这一点的一个好方法是设定超类采取的某些行动的期望 - 示例:

    class Some::Thing < Some
     def instance_method
        super
     end
    end
    

    和超类:

    class Some
      def instance_method
         another_method
      end
    
      def self.another_method # not private!
         'does a thing'
      end
    end
    

    现在测试:

     describe '#instance_method' do 
        it 'appropriately triggers the super class method' do
          sawm = Some::Thing.new
          expect(sawm).to receive(:another_method)
          sawm.instance_method
        end
     end
    

    这一切都决定了在超类上调用了 Super

    此模式的有用性取决于您如何构建测试/通过应用super 方法对子/派生类的突变有什么期望。

    另外 - 密切关注 classinstance 方法,您将需要相应地调整 allowsexpects

    YMMV

    【讨论】:

      【解决方案3】:

      正如@myron 建议的那样,您可能想要测试super 中发生的行为。

      但如果你真的想这样做,你可以这样做:

      expect_any_instance_of(A).to receive(:instance_method).and_call_original
      

      假设

      class B < A
        def instance_method
          super
        end
      end
      
      class A
        def instance_method
          #
        end
      end
      

      免责声明expect_any_instance_of是弱测试的标志(see):

      此功能有时在处理遗留代码时很有用,不过 一般来说,我们不鼓励使用它的原因有很多:

      rspec-mocks API 是为单个对象实例设计的,但是 此功能适用于整个对象类别。结果那里 是一些语义上令人困惑的边缘情况。例如,在 expect_any_instance_of(Widget).to receive(:name).twice 不清楚 一个特定的实例是否预计会收到两次名称,或者如果 预计共收到两次。 (是前者。)

      使用此功能通常带有设计味道。可能是您的测试试图做的太多或者被测对象太 复杂。

      它是 rspec-mocks 中最复杂的功能,历史上收到的错误报告最多。 (没有核心团队 积极使用它,这没有帮助。)

      【讨论】:

        【解决方案4】:

        这个聚会有点晚了,但你也可以放弃使用 super 关键字,而是这样做

        class Parent
          def m(*args)
          end
        end
        
        class Child < Parent
          alias super_m m
        
          def m(*args)
            super_m(*args)
          end
        end
        

        这样你的超级方法就可以像任何其他方法一样被访问,例如像任何其他方法一样被存根。主要的缺点是您必须显式地将参数传递给对超级方法的调用。

        【讨论】:

          猜你喜欢
          • 2013-04-13
          • 1970-01-01
          • 1970-01-01
          • 2012-01-05
          • 2023-03-26
          • 2020-08-31
          • 2022-11-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多