Reputation: 30286
I'm in Rails 6 with Rspec 3.8.0.
I have a model A
which belongs_to
B. And I'm trying to write a unit test with A
as subject
:
expect(subject.b).to receive(:to_s)
subject.my_fn
Yet this spec always fails, saying that the instance of B
did not receive the message, notwithstanding I have put binding.pry
in the actual code to run and verified that a.b.to_s
gets called:
class A
def my_fn
binding.pry
b.to_s
end
end
I have even tried:
expect(a).to receive(:b).and_return(b)
expect(b).to receive(:to_s)
And:
expect_any_instance_of(b.class).to receive(:to_s)
Yet all expectations for to_s
fail. Why is this?
Upvotes: 2
Views: 2682
Reputation: 6603
It's not shown in your code, but I have a feeling that you are calling the code
before you set up your "receive" expectations. Simply put, the code execution should be like below:
it 'something' do
expect(subject.b).to receive(:to_s)
# write code here that would eventually call `a.b.to_s` (as you have said)
# i.e.
# `subject.some_method` (assuming `some_method` is your method that calls `a.b.to_s`
# don't call `subject.some_method` before the `expect` block above.
end
expect(THE_ARG) ... receive()
and the object that you are testing to be called. You can verify that they are the same if they have the same object_id
:it 'something' do
puts subject.b.object_id
# => 123456789
subject.some_method
end
# the class/method you're unit-testing:
class Foo
def some_method
# ...
puts b.object_id
# => 123456789
# ^ should also be the same
Otherwise if it's not the same object (object_id does not match), you would have to either use expect_any_instance_of
(which I only use at the last resort as it is potentially dangerous as it is expecting "any instance")... or you could stub the chain a.b.to_s
objects in your spec file.
If it's hard to stub the whole chain but at the same time, avoid the pitfalls of using expect_any_instance_of
, there's another variant that I use which I use to balance convenience and spec-accuracy:
it 'something' do
expect_any_instance_of(subject.b.class).to receive(:to_s).once do |b|
expect(b.id).to eq(subject.b.id)
# the above just compares the `id` of the records (even if they are different objects in different memory-space)
# to demonstrate, say I do puts here:
puts b
# => #<SomeRecord:0x00005600e7a6f3b8 id:1 ...>
puts subject.b
# => #<SomeRecord:0x00005600e4f04138 id:1 ...>
puts b.id
# => 1
puts subject.b.id
# => 1
# notice that they are different objects (0x00005600e7a6f3b8 vs 0x00005600e4f04138)
# but the attribute id is the same (1 == 1)
end
subject.some_method
end
Upvotes: 3
Reputation: 181
Seems that makes more sense you stub the b
relation. It will looks like:
expect(a).to receive(:b).and_return(stub(:b, to_s: 'foo_bar')
Upvotes: 0