I have no idea if this is silly, or if everyone-and-their-dog already does this, or if this is novel. Basically, I wanted a way to have a "controlled scope" – I wanted my mock to live only as long as I wanted it to, but I didn't want to have that be within a lexical constraint. (Naturally, if all I cared about were a lexical scope, I would have used local)
Motivation:
My solution:
So what I did was create a "scoping token". It's just an empty scalar that has a destructor "attached" to it to handles the cleanup. I suppose this could have been done using overloading (a la Scalar::Defer) but this solution seemed like it would be faster and cleaner.
Here's my method mock_class_constructor(). It takes as its arguments the name of the constructor symbol you are mocking (that is, a fully qualified method name) and the object (assumably a Test::MockObject, but could be anything) that you want it to return.
sub mock_class_constructor {
my $proto = shift;
my($constructor_symbol, $return_object) = @_;
no strict 'refs';
no warnings 'redefine';
my $orig_constructor = \&{ "$constructor_symbol" };
*{ "$constructor_symbol" } = sub { $return_object };
# orderly cleanup
my $package = int(rand(time)) . int(rand($$));
no strict 'refs';
*{ $package . '::DESTROY' } = sub {
# Actual cleanup
no warnings 'redefine';
*{ "$constructor_symbol" } = $orig_constructor;
# Cleanup of this closure
undef *{ $package . '::DESTROY' };
};
return bless \do { my $anon_scalar }, $package;
}
So now in my test code, when I want to unit test with the boundary class mocked, I call this first. If I want to switch to a longer integration test, I'll release the token and run this again on a different class (further down the stack.) (Naturally, I don't need this at all for a full-stack integration test.)
Yes, I know I could have had the method accept a closure instead of just an object to return. At that point though I'd just be re-implementing Test::MockModule.
My original design was to have the method return a callback closure that does the cleanup. I dumped this for two reasons:
With this design, I could do any number of more "natural" scope controls:
local())weaken() the $return_object reference otherwise the closure will have a circular reference.So I feel like I've got something that might have some useful application, but I also imagine that it's a dead end someone else has already found. I'm interested in any feedback.
Excellent name! (Score:1)
Would it be possible to show the code in which this is used? I'm having trouble thinking of a situation in which local woudn't cut it (although there obviously is if several different people have thought of this technique).
I really like the name "scripting token". My first thought was to call the scalar a "canary"
Scoping token in action (Score:1)
I can give a little, though I think in most cases I didn't use it in as novel ways as I conceived that I would. (I'll have to redact a bit, but that's okay.) But I have a mean over-engineering streak...
The method in the journal entry exists in my project Testing superclass (which isa Test::Class.) In addition, there is:
Object::Destroyer seems similar (Score:1)