6

所以我有一些代码,大大简化,看起来像这样:

class B
  def initialize opts
    @opts = opts
  end
end

class A
  def initialize opts
    # defaults etc applied to opts
    @b = B.new opts
  end
end

换句话说,当我用选项初始化 A 时,它会创建一个 B 并将一组修改后的选项传递给它。

我想测试 B.new 是否获得了正确的论点。现在,我正在这样做,使用 RSpec/RR:

@b = Object.new
# stub methods on @b here
stub(B).new { |options|
  options[:foo].should == 'whatever'
  @b
}
A.new({:foo => 'whatever'})

但这有两个问题。

首先,我无法B使用实际选项实例化实际副本。如果我在块内调用 B.new,它会调用存根版本并循环直到堆栈弹出。我可以@b = B.new在 stubbing 之前设置,但是我还不知道将要传入的选项,从而破坏了测试的重点。

(在有人叫我之前:是的,在严格的单元测试教条中,对 A 的测试应该剔除 B 中的任何方法,并且需要大量剔除意味着你的代码首先是糟糕的。)

其次,将它放在测试的设置中感觉不对should,而不是之后放在单独的it ... do ... end块中。但是由于我不能创建一个实际的B(见上文),我也不能真正询问它的构建后状态。

有任何想法吗?

4

3 回答 3

10

should来自Marc-André Lafortune 的答案的语法在 RSpec 3 中似乎已被弃用。但是,以下expect语法似乎有效:

expect(B).to receive(:new).with(foo: 'whatever')

请注意,如果您想B.new返回特定实例(例如测试替身),您可以使用and_return

b = instance_double(B)
expect(B).to receive(:new).with(foo: 'whatever').and_return(b)
于 2015-03-18T22:18:42.137 回答
3

你可以写类似B.should_receive(:new).with({:foo => 'whatever'}).

就个人而言,我避免打断/嘲弄,宁愿测试行为;使用给定的一组选项创建一个新的事实B是依赖于实现的,我不会直接测试它。

于 2012-07-06T05:19:23.810 回答
1

RR 版本的 Marc-Andre 的回答:

before do
  stub(B).new { @b }
end

it 'uses the correct options' do
  B.should have_received.new(hash_including(:foo => 'whatever'))
end
于 2012-07-06T05:58:12.390 回答