【问题标题】:rspec: How to stub an instance method called by constructor?rspec:如何存根构造函数调用的实例方法?
【发布时间】:2010-09-23 21:31:47
【问题描述】:
class A
  def initialize
    @x = do_something
  end

  def do_something
    42
  end
end

如何在调用原始实现之前在 rspec 中存根 do_something(因此将 42 分配给 @x)?当然,无需更改实现。

【问题讨论】:

    标签: ruby rspec stub


    【解决方案1】:

    Here's the commit which adds the feature to rspec - 这是 2008 年 5 月 25 日。你可以这样做

    A.any_instance.stub(do_something: 23)
    

    但是,rspec 的最新 gem 版本(1.1.11,2008 年 10 月)中没有这个补丁。

    This ticket 表示他们出于维护原因将其拉出,但尚未提供替代解决方案。

    目前看来你做不到。您必须使用 alias_method 或类似的方法手动破解该类。

    【讨论】:

    • @rogerdpack:确实,终于! blog.davidchelimsky.net/2011/05/12/rspec-260-is-released "添加对 any_instance.stub 和 any_instance.should_receive 的支持"
    • 如果您想将某些东西直接附加到由 new 创建的实例上,您知道吗?像 should_receive(:blah).exactlY(5).times --- 你不想要任何对象,只是那个对象?
    • @xaxxon 一种方法是使用any_instance 创建存根,然后构建对象,然后在再次调用构造函数之前使用rspec_reset 撤消所有存根。注意 rspec_reset 没有记录,可能不受官方支持。您可以在任何对象上调用它以删除该对象的存根
    【解决方案2】:

    我在http://pivotallabs.com/introducing-rr/找到了这个解决方案

    new_method = A.method(:new)
    
    A.stub!(:new).and_return do |*args|
      a = new_method.call(*args)
      a.should_receive(:do_something).and_return(23)
      a
    end
    

    【讨论】:

      【解决方案3】:

      我不知道如何在规范的模拟框架中做到这一点,但您可以轻松地将其换成 mocha 来执行以下操作:

      # should probably be in spec/spec_helper.rb
      Spec::Runner.configure do |config|
        config.mock_with :mocha
      end
      
      describe A, " when initialized" do
        it "should set x to 42" do
          A.new.x.should == 42
        end
      end
      
      describe A, " when do_something is mocked" do
        it "should set x to 23" do
          A.any_instance.expects(:do_something).returns(23)
          A.new.x.should == 23
        end
      end
      

      【讨论】:

        【解决方案4】:

        RR:

        stub.any_instance_of(A).do_something { 23 }
        

        【讨论】:

          【解决方案5】:

          在今天最新版本的 RSpec - 3.5 中,您可以:

          allow_any_instance_of(Widget).to receive(:name).and_return("Wibble")
          

          【讨论】:

            【解决方案6】:

            我确实喜欢 Denis Barushev 的回答。而且我只想建议一种外观上的改变,这使得new_method 变量变得不必要。 Rspec 确实对存根方法进行了处理,因此可以使用 proxied_by_rspec__ 前缀访问它们:

            
            A.stub!(:new).and_return do |*args|
              a = A.proxied_by_rspec__new(*args)
              a.should_receive(:do_something).and_return(23)
              a
            end
            

            【讨论】:

            • 这是一个非常好的主意,但它无法与我正在使用的 RSpec 版本一起使用。丹尼斯的回答确实有效。
            • 谢谢!是的,它非常依赖于“proxied_by_rspec__”名称前缀约定,这可能不是已发布 API 的一部分。哦,对不起,第二行不需要“.call”。顺便说一句,您使用的是哪个 RSpec 版本?
            【解决方案7】:

            在 RSpec 2.6 或更高版本中,您可以使用

            A.any_instance.stub(do_something: 23)
            

            请参阅the docs 了解更多详情。 (感谢rogerdpack 指出现在这是可能的——我认为它应该得到自己的答案)

            【讨论】:

              【解决方案8】:

              要存根实例方法,您可以执行以下操作:

              before :each do
                @my_stub = stub("A")
                @my_stub.should_receive(:do_something).with(no_args()).and_return(42)
                @my_stub.should_receive(:do_something_else).with(any_args()).and_return(true)
                A.stub(:new).and_return(my_stub)
              end
              

              但正如 pschneider 指出的那样,只需在 new 上返回 42 : A.stub(:new).and_return(42) 或类似的东西。

              【讨论】:

              • 我认为关键是当您无法控制在测试中创建实例时对实例方法进行存根。另外,初始化器不返回 42,它设置了一个实例变量:它是 do_something 方法返回 42 需要被存根。
              • A.stub(:new).and_return(@my_stub) 每次都会返回实例存根,从而让您控制实例的创建。至于设置成员变量,我一直听说您不应该担心模拟/存根中的内部结构,只需担心公共接口。因此,只需为依赖于@x 的方法返回正确的值。我可能错了
              【解决方案9】:

              这是一个可能不是很优雅但基本上肯定会奏效的想法:

              创建一个继承您要测试的类的小类,覆盖初始化方法并调用super在初始化中创建存根之后,如下所示:

              it "should call during_init in initialize" do
                class TestClass < TheClassToTest
                  def initialize
                    should_receive(:during_init)
                    super
                  end
                end
                TestClass.new
              end
              

              你去吧!我刚刚在我的一项测试中成功使用了它。

              【讨论】:

                【解决方案10】:

                rspec_candy gem 带有一个stub_any_instance 辅助方法,该方法在 RSpec 1 和 RSpec 2 中都有效。

                【讨论】:

                  【解决方案11】:

                  在我的 rspec (1.2.2) 版本中,我可以这样做:

                  A.should_receive(:new).and_return(42)
                  

                  我知道回答原始海报可能为时已晚,但我还是回答了它以供将来参考,因为我带着同样的问题来到这里,但发现它正在使用最新的 rspec 版本。

                  【讨论】:

                  • 您的建议行为不正确。它是返回 42 的 :do_something 方法,而不是初始化程序。
                  猜你喜欢
                  • 1970-01-01
                  • 2022-11-23
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2019-06-10
                  • 1970-01-01
                  • 2021-12-13
                  相关资源
                  最近更新 更多