So I've got some code that, grossly simplified, looks like this:
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
In other words, when I initialize A with options, it creates a B and passes a modified set of options to it.
I would like to test that B.new gets the correct arguments. Right now, I'm doing it like this, using RSpec/RR:
@b = Object.new
# stub methods on @b here
stub(B).new { |options|
options[:foo].should == 'whatever'
@b
}
A.new({:foo => 'whatever'})
But this has two problems.
First, I can't instantiate an actual copy of B
with the actual options. If I call B.new inside the block, it calls the stubbed version and loops until the stack pops. I can set @b = B.new
before the stubbing, but I don't know the options that will be passed in yet, defeating the point of the test.
(And before somebody calls me out on it: yes, in strict unit test dogma, a test of A should stub out any methods in B, and needing to stub out a lot means your code is bad in the first place.)
Second, it just feels wrong to put the should
in the setup of the test, instead of in a separate it ... do ... end
block afterwards. But since I can't create an actual B
(see above), I can't really interrogate its post-construction state either.
Any ideas?
The should
syntax from Marc-André Lafortune's answer appears to be deprecated in RSpec 3. The following expect
syntax seems to work, however:
expect(B).to receive(:new).with(foo: 'whatever')
Note that if you want B.new
to return a specific instance (e.g. a test double), you can use and_return
:
b = instance_double(B)
expect(B).to receive(:new).with(foo: 'whatever').and_return(b)