Cute trick. It's actually something I'd considered but rejected (but I would say that wouldn't I?), but your wrapper is very neat.
The problem with it, as I see it is that, with optimisitic typing (ie, just passing in $self), you get an immediate failure if the object under test calls an unexpected method simply because you haven't implemented it. With a localized @ISA, Logger's methods becomes callable and an unexpected method call could get dispatched to real code (and you can't be sure that that would throw an exception.
If Params::Validate does the right thing ($obj->isa('foo'), not UNIVERSAL::isa($obj, 'foo')) then you could always do
sub _do_as (&$) {
my($block, $fake_class) = @_;
local *Object::Test::isa = sub {
my($self, $target_class) = @_;
return $target_class eq $fake_class ||
$self->SUPER::isa($target_class);
};
$block->();
}
If the validater calls
UNIVERSAL::isa, you could override that instead, but it would be rather more awkward. And it's all a good deal more awkward than just chucking the test object in without adornment.
This really comes down to how much value you see in the strict typing of variables. Personally I am unconvinced by it. I'd prefer to see a good test suite (and Smalltalk style method selectors, but they're rather harder to come by in Perl. Smalltalk method selectors are great though. They give you lots more naming opportunities, your 'new' method would become
newWithLogger: aLogStream
^ self new setLogger: aLogStream
which isn't necessarily the most convincing of arguments, but believe me it's great with more complex method signatures...)