Reputation: 129
In a method I have multiple calls to another method with different arguments. I only want to test one particular call to see if the arguments for that call match some condition. Is there a better way to do so than stubbing every other call?
For example, I have
def some_method
foo(1)
foo('a')
foo(bar) if ... # some complex logic
foo(:x)
...
end
I just want to test if foo
is actually called with argument bar
.
subject.should_receive(:foo).with(correct_value_of_bar)
But what to do with other calls of foo
inside the same some_method
?
Upvotes: 1
Views: 592
Reputation: 1784
Okay, so based on your latest comment you want to observe that you're logging some output. Here's an example where you observe this behavior by replacing STDOUT with a StringIO instance:
# 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
This will work, but doing this has side-effects that reach far beyond your specific example because we're changing the global $stdout
. This can be improved by using poor man's dependency injection with constructor defaults:
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
In the above example we give ourselves the ability to pass in the IO object that we'll be putting to. This lets us observe the behavior without having side-effects beyond the scope of the test and in a way that lets the object we're testing stay true to itself (ie: we're not modifying the object itself as with the previous but now deleted comment about using as_null_object
suggested).
You could also use an options hash on your constructor and push the lazy assignment into initialize
itself:
def initialize(arg1, arg2, options={})
@io = options[:io] || STDOUT
end
And you could also upgrade your simple puts
to use an actual Logger object. Then you could test in one place that your logger worked with STDOUT, STDERR or where ever and you could test in all of the objects where you cared about logging that it was logging to info
, debug
, etc appropriately.
You could take this in a few more directions as well, but without knowing more about what you're doing this potential answer is likely already long enough.
Hopefully this gives you some ideas as to how you could approach this by observing the behavior rather than relying on internal implementation details (like the fact the you're using puts
itself as opposed to print "bar\n"
or another method which outputs text to an IO object.
Upvotes: 1