如何在RSpec中只测试多个方法调用之一?

时间:2013-07-17 17:18:59

标签: ruby rspec

在一个方法中,我有多个调用另一个带有不同参数的方法。我只想测试一个特定的调用,看看该调用的参数是否符合某些条件。有没有更好的方法来阻止其他所有的电话?

例如,我有

def some_method
  foo(1)
  foo('a')
  foo(bar) if ... # some complex logic
  foo(:x)
  ...
end

我只想测试foo是否实际使用参数bar调用。

subject.should_receive(:foo).with(correct_value_of_bar)

但是如何处理同一foo内的some_method的其他来电?

1 个答案:

答案 0 :(得分:1)

好的,基于您的最新评论,您希望观察到您正在记录一些输出。这是一个通过用StringIO实例替换STDOUT来观察此行为的示例:

# foo.rb
require 'rubygems'
require 'rspec'
require 'rspec/autorun'

require 'stringio'

class Foo
  def something
    puts 1
    puts 'a'
    puts 'bar' if true # some complex logic
    puts :x
  end
end


describe Foo do
  describe '#something' do
    context "and something complex happens" do
      let(:io){ StringIO.new }
      it "logs to STDOUT" do
        $stdout = io
        Foo.new.something
        expect(io.tap(&:rewind).read).to include("bar\n")
      end
    end
  end
end

这样可行,但这样做的副作用远远超出了您的具体示例,因为我们正在更改全局$stdout。这可以通过使用穷人的依赖注入和构造函数默认值来改进:

class Foo
  def initialize(io=STDOUT)
    @io = io
  end

  def something
    puts 1
    puts 'a'
    puts 'bar' if true # some complex logic
    puts :x
  end

  protected

  def puts(*args)
    @io.puts *args
  end
end


describe Foo do
  describe '#something' do
    context "and something complex happens" do
      let(:io){ StringIO.new }
      it "logs to STDOUT" do
        Foo.new(io).something
        expect(io.tap(&:rewind).read).to include("bar\n")
      end
    end
  end
end

在上面的例子中,我们给自己传递了我们将要放入的IO对象的能力。这让我们可以观察行为而不会产生超出测试范围的副作用,并且让我们正在测试的对象保持对自己的真实性(即:我们不像以前那样修改对象本身但是现在删除了关于使用建议的as_null_object的评论。

您还可以在构造函数上使用选项哈希,并将惰性赋值推送到initialize本身:

def initialize(arg1, arg2, options={})
   @io = options[:io] || STDOUT
end 

您还可以升级简单puts以使用实际的Logger对象。然后你可以在一个地方测试你的记录器使用STDOUT,STDERR或任何地方,你可以测试你记录它记录到infodebug等的记录的所有对象适当。

你也可以在更多的方向上采取这个方法,但是如果不知道你正在做什么,这个潜在的答案可能已经足够长了。

希望通过观察行为而不是依赖于内部实现细节(例如您使用puts本身而不是print "bar\n"这一事实,这可以为您提供一些关于如何处理此问题的建议。或者另一种将文本输出到IO对象的方法。