-
-
Notifications
You must be signed in to change notification settings - Fork 356
Provide const_double alias for class_double. #424
Conversation
This is a good start, and it's nice to see that Two things:
it 'can create a double that matches the interface of any arbitrary object' do
o = const_double(LoadedClass.new)
prevents { expect(o).to receive(:undefined_instance_method) }
prevents { expect(o).to receive(:defined_class_method) }
prevents { o.defined_instance_method }
expect(o).to receive(:defined_instance_method)
o.defined_instance_method
end
|
|
Weird, |
To me it's odd because the most common use of constants is for class names. So to then pass an object that's not a 'class' to it seems weird. Could just be me though ;) |
I'm going to build this on top of #422 once that lands. |
So the code is easy, figuring out how to name and guard everything is hard... I'm currently trying to figure out (thinking out loud):
Obviously the current diff is WIP, I haven't changed any names or documentation yet. |
OK I think I've addressed all my thinking out loud above, this is ready for review again. @myronmarston in particular read over the added feature, because I pretty much crimped it all from stuff you've said in comments :) This isn't a feature I've felt a strong need for personally, so I may not have done it justice. Weakest part at the moment is probably the feature for constant replacement. Suggestions welcome. |
constructable, but may have far-reaching side-effects such as talking to a | ||
database or external API. In this case, using a double rather than the real | ||
thing allows you to focus on the communication patterns of the object's | ||
interface without having to worry about accidentally causing side-effects. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a really excellent description of object_double
; nice work!
One other use case that might be worth mentioning is doubling an instance of a class that heavily uses method_missing
. IIRC, I originally thought up object_double
when you were documenting how to use the verified doubles feature for ActiveRecord::Base
subclasses, since it uses method_missing
so much, and the idea came out of that (as if you have the class loaded, using object_double
seems preferrable to the other work arounds such as defining column attribute methods). Do you think it's worth mentioning something about this use case as well?
You're probably still reading, but I have addressed your first three comments. |
I seem incapable of ever submitting a PR that is 1.8 compatible :/ |
|
||
def when_loaded(&block) | ||
block.call @object | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just do:
def when_loaded
yield @object
end
I've read on multiple occasions that capturing the block as a proc (&block
) carries a perf penalty compared to using yield
(which makes some sense; procs have to support some things that yield
doesn't, such as answering questions about arity, etc).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just use &block
out of habit, no good reason. yield
works too.
I like the idea of a factory method. However, I'm wondering if the difference we're looking for isn't so much module vs. object, but named reference (when given a string form of a constant) vs direct reference (when given an object directly). Looking at your implementation, I think there's some weirdness that reorganizing on this axis may help resolve:
Putting these together, I think there are 3 cases, and 3 classes that would result: def reference_for(object)
case object
when String then NamedObjectReference.new(object)
when Module then DirectModuleReference.new(object)
else DirectObjectReference.new(object)
end
end
I might be missing something but that seems to handle (and represent) all the cases well.
I like that. |
This turned out to not be necessary, instead there is just a conditional in
No, it's only used for exception messages. In this case, it does make sense on
In that case, a
I believe I have verified all the error messages in the spec, including this one, and checked that they are clear. Counter-examples welcome.
I came to the same conclusion, and that is current behaviour. I added a spec to verify the |
green build |
(leaves to @myronmarston to review) |
This is looking really good, @xaviershay. I have some responses to our conversation above, but these aren't merge blockers. I'd be happy to merge this as-is (once green) and submit a PR of my own to do the refactorings I have in mind (see below).
I looked into this some more and it looks like we're both right.
...which is the spot I was thinking of. It is also used in the error message here:
...which is a use of I think that while the same string works well for these two cases for a Also, should it be
Your specs cover all the cases well. I do think, however, that having a Anyhow, one last suggestion: the dynamic_classes cuke should perhaps be revised in light of the new |
I applied this refactoring, though used
I was on the fence, after thinking about it some more I think
Let's leave this for another PR. I started going down this route just now and didn't like how it was turning out, so I'd like to debate that separately.
Added a sentence |
...I should probably save the commit squashing until we're done so you can see the diff. Sorry about that. |
`ActiveRecord` does this to define methods from database columns. | ||
`ActiveRecord` does this to define methods from database columns. If the | ||
object has already been loaded you may consider using an `object_double`, but | ||
that cannot work if you are testing in isolation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could actually test it in isolation with something like this:
# user.rb
class User < ActiveRecord::Base
EXAMPLE_INSTANCE = new
end
# in the spec
user = object_double("User::EXAMPLE_INSTANCE")
...but it's kinda weird.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to leave that as an easter egg for people to discover themselves :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, I already merged this since I'm not sure I want to document that. Still, it's neat that it falls out of this is a workable solution :).
Provide const_double alias for class_double.
Great work, @xaviershay! |
References #391.
@myronmarston this seemed the simplest solution. Is there any behaviour you'd expect to be different? How do you feel about
const_double
instead ofobject_double
? I felt like it was a more accurate name, sitting alongsideclass
.