-
-
Notifications
You must be signed in to change notification settings - Fork 359
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
instance_double not matching the same class it is mocking #794
Comments
This is by design. |
To expand on this a bit more...besides the fact that |
I don't think it's true that you can work around this with a partial double. Given the following: class Foo
end
class Bar
end
class Baz
def frob(x)
case x
when Foo
'foo'
when Bar
'bar'
else
raise "expected Foo or Bar, got: #{x}"
end
end
end
require 'rspec'
describe Baz do
describe '#frob' do
it 'frobs a Foo' do
foo = double(Foo.new)
baz = Baz.new
expect(baz.frob(foo)).to eq('foo')
end
end
end I get:
In terms of test simplicity, if you don't need your 'mock' to do anything but you want to avoid some complex initialization, you can use |
You're example isn't using a partial double. (At least, it's not using what we mean by "partial double").
True, although |
You're right, I totally failed to understand the partial double documentation. Sorry, coming out of Mockito where you have to wrap any real object before you can stub its methods. (The fact that the return values in the partial double docs are also doubles is confusing to newcomers, or at least this newcomer.) If I wanted to stub |
You're right. Problem is, it doesn't really make sense to provide In the meantime, you could do this: module VerifiedDoubleExtensions
def instance_double(klass, *args)
super.tap do |dbl|
allow(klass).to receive(:===).and_call_original # do the normal thing by default
allow(klass).to receive(:===).with(dbl).and_return true
end
end
end
RSpec.configure do |c|
c.include VerifiedDoubleExtensions
end This would make |
Clever! Thanks. |
@myronmarston , I understand an instance_double's design idea you explained, but if you look at Module#=== implementation, it actually calls the operand's kind_of? method. So, we can get a "full" behavior by: let(:test) { instance_double(Integer) }
allow(test)
.to receive(:kind_of?).with(Integer)
.and_return(true) Of course, other methods would have to be mocked also (#class, #is_a? etc). This could be another design approach ("instance_double_on_steroids"?!). What do you think? |
@fornellas that sound like a good idea for an extension gem. If/when it sees significant usage by the community, we might consider rolling it in to RSpec in a future release. |
D'oh! Although Module#=== calls rb_obj_is_kind_of(), it in turn, does not seem to call the mock on the instance double. This approach does not work. One (hard) option would be to use a real object, created with BasicObject.allocate, and then, overwrite all existing methods on the class hierarchy, plus method_mising, to mimic instance_double. Even then, if the class hierarchy changes, things would break. We'd have to use hooks to protect against it. |
In practice I've mostly been able to work around it using |
@dmolesUC3 , that would be appropriate. The disadvantage over instance doubles, is that an allocated instance won't raise upon a not mocked method, and since it does not call #initialize, you'd surely get some odd behaviors... |
It does depend on exactly what you're testing, certainly. With luck (and attention to detail) you won't have too many cases where the "double" needs to pass |
It seems that stubbing those calls to (Ruby 2.2+) |
Ah, actually there is a way to do it directly via
|
Just came across this and thought I'd add my solution (which borrows from a few posted here) for anybody else that comes across this. Delegating to # Updated based on https://github.com/rspec/rspec-mocks/issues/794#issuecomment-667199482
module VerifiedDoubleExtensions
CLASS_EQUIVALENCE_FUNCTIONS = %i[is_a? kind_of? instance_of?].freeze
def verified_double(klass, *args)
instance_double(klass, *args).tap do |dbl|
CLASS_EQUIVALENCE_FUNCTIONS.each do |fn|
allow(dbl).to receive(fn) do |*fn_args|
klass.allocate.send(fn, *fn_args)
end
end
allow(klass).to receive(:===).and_call_original # do the normal thing by default
allow(klass).to receive(:===).with(dbl).and_return true
end
end
end See https://gist.github.com/thefotios/23912e524f585d855606dbec713df388 for history |
|
@JonRowe agreed. That sort of data should be irrelevant to checking for
|
I have a test case wherin an
instance_double
of a class does not think it is akind_of
/is_a
/===
of the mocked class.https://gist.github.com/plukevdh/7438f96ec0bd4dd65de8
May be related to issue filed/closed #749. Am I misunderstanding how this ought to work?
Currently using RSpec 3.1.5
The text was updated successfully, but these errors were encountered: