如何正确存根双打

时间:2015-03-23 17:54:32

标签: ruby rspec mocking stubbing rspec3

正在测试的代码:

class Session
  def initialize
    @interface = Interface.new(self)
    @interface.hello
  end
end

class Interface
  def initialize(session, out = $STDOUT)
    @session = session
    @out = out
  end

  def hello
    @out.puts "hello"
  end
end

测试:

describe Session do
  let (:fake_stdout) {double("$STDOUT", :puts => true)}
  let (:interface) {instance_double("Interface", :out => "fake_stdout")}
  let (:session) { Session.new }

  describe "#new" do
    it "creates an instance of Session" do
      expect(session).to be_an_instance_of(Session)
    end
  end
end

这会引发private method 'puts' called for nil:NilClass。它似乎没有看到fake_stdout指定的:putsout。我尝试用allow(Interface).to receive(:new).with(session).and_return(interface)绑定它,但这没有改变。如何让经过测试的Session类看到double / instance double并通过测试?

1 个答案:

答案 0 :(得分:0)

我认为,这不是真正的存根问题,而是一般方法。在为某些类编写单元测试时,您应该坚持使用该类的功能,并最终使用它所见到的API。如果您正在对out的“内部”Interface进行存根 - 那么Session的规格已经很多了。

Session真正看到的是Interface公共hello方法,因此Session规范,不应该知道它的内部实现(它是{{ 1}})。你唯一应该关注的是,已经调用了@out.puts "hello"方法。另一方面,确保为hello调用put时,应在hello的规范中进行说明。

Ufff ......那是很长的介绍/解释,但是如何继续呢? (称为告诉我代码!;)。

说过,Interface应该只知道Session.new Interface方法,它应该相信它能够正常运行,并且hello规范应该确保方法叫做。为此,我们将使用Session。让我们弄脏手!

spy
  

测试间谍是一个记录参数,返回值,此值以及所有调用抛出异常(如果有)的函数。

这取自SinonJS(这是搜索“什么是测试间谍”时的第一个结果),但解释是准确的。

这是如何工作的?

RSpec.describe Session do
  let(:fake_interface) { spy("interface") }
  let(:session)        { Session.new }

  before do
    allow(Interface).to receive(:new).and_return(fake_interface)
  end

  describe "#new" do
    it "creates an instance of Session" do
      expect(session).to be_an_instance_of(Session) # this works now!
    end

    it "calls Interface's hello method when initialized" do
      Session.new
      expect(fake_interface).to have_received(:hello)
    end
  end
end

首先,我们正在执行一些代码,之后我们断言期望发生事情。从概念上讲,我们希望确保在Session.new expect(fake_interface).to have_received(:hello) Session.new fake_interface期间。这就是全部!

好的,但我需要另一个测试,确保使用特定的参数调用have_received(:hello)方法。

好的,让我们测试一下!

假设Interface看起来像:

Session

我们要测试class Session def initialize @interface = Interface.new(self) @interface.hello @interface.say "Something More!" end end

say

这个很简单。

还有一件事 - 我的RSpec.describe Session do describe "#new" do # rest of the code it "calls interface's say_something_more with specific string" do Session.new expect(fake_interface).to have_received(:say).with("Something More!") end end end Interface为参数。如何测试Session调用interface方法?

让我们看一下示例实现:

session

如果我说......那就不会太令人惊讶了。

class Interface
  # rest of the code

  def do_something_to_session
    @session.a_session_method
  end
end

class Session
  # ...

  def another_method
    @interface.do_something_to_session
  end

  def a_session_method
    # some fancy code here
  end
end

如果RSpec.describe Session do # rest of the code describe "#do_something_to_session" do it "calls the a_session_method" do Session.new.another_method expect(fake_interface).to have_received(:do_something_to_session) end end end Session调用了another_method interface方法,则应该检查。

如果你这样测试,你会使测试对未来的变化不那么脆弱。您可以更改do_something_to_session的实施,它不再依赖Interface。引入此类更改时 - 您必须仅更新put的测试。 Interface只知道调用了正确的方法,但内部会发生什么?这是Session的工作......

希望有所帮助!请查看我other answerInterface的另一个例子。

祝你好运!