Limitations of it_behaves_like
While implementing shared specs for ruby/spec#576, I noticed some limitations in the implementation of it_behaves_like that should be addressed.
Issues Discovered
-
All it_behaves_like calls must have an identical
@methodand@objectcalls within a given scope, or they will affect each others' state. To overcome this, you have to wrap the differing parameter in acontextordescribeblock. -
If you have a shared spec that relies upon another shared spec, the above issue gets more complex, because even if you've wrapped it in a describe block, the
@methodhas shared scope. To get around this, I have saved the outer@methodinto a new@base_methodinside a before and referred to it via@base_methodto solve the collision.
Proposed Solution
Instead of mapping to @method and @object, scoping these to instance variables specific to the behaves_like description:
def it_behaves_like desc, meth, obj=nil
send :before, :all do
@shared_bindings ||= {}
@shared_bindings[desc] = { method: meth, object: obj }
end
end
I haven't fully fleshed this out, but if it sounds like a reasonable approach I can go further on a fleshed out solution and PR accordingly.
FWIW, I recently noticed the sprtintf-related specs didn't work as intended (ending up using the same block saved in @method for all usages for the nested case).
Essentially, nested it_behaves_like doesn't work currently as this issue says.
OTOH, it seems we have very few cases where nested shared examples are needed, so I'd be inclined to simply prevent it by raising an exception in MSpec if we can easily detect the nested case.
Your proposed solution looks fine, but given nested shared examples are so hard to think about I'd rather just not have that complication.