"Scoping token" for class constructor mocking

dpisoni on 2006-10-26T23:52:21

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:
  1. Unit Testing
  2. I use Test::Class, which supports the notion of xUnit-style "fixtures", and runs all tests in one interpreter
  3. I am using Test::MockObject to mock objects of my boundary classes, and manually mocking the class constructors
  4. I want the freedom to set up my mocks in a particular "scope" under my complete control. So I want to re-use the actual MockObjects (to better enable changing out the data emitted by the mock methods without having to set them up over and over) but dynamically set up and tear down the constructor mocking.
  5. I use extensively the features of Test::MockObject in my test code. I have no need for any special features of Test::MockModule or Test::MockClass, all I need is to intercept a single constructor method and then restore it back to its original state when I'm done with it.
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:

  • dangerous: no way to ensure that the cleanup would actually be called
  • less useful: couldn't opt to piggyback on an existing scope

With this design, I could do any number of more "natural" scope controls:

  • Confine the token to a lexical scope, cleanup happens when scope terminates. (no real benefit here, could have done this with local())
  • Stash the token in another object's instance data. This essentially binds the scope to that object's, sort of a "piggyback scope". Probably could do this with the MockObject, though I'd have to change the code above to weaken() the $return_object reference otherwise the closure will have a circular reference.
  • Explicity release the token

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!

DAxelrod on 2006-10-27T01:41:12

I'd also considered using a similar technique for similar scoping reasons. Make sure you're handling reentrancy correctly (although this may not be a problem if this technique is for unit testing).

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", because it exists only to trigger other behavior when it is destroyed, vaguely like a canary in a mineshaft; but your name is much more descriptive.

Scoping token in action

dpisoni on 2006-10-27T04:50:09

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:

sub mock_network {
    my $proto = shift;
    $proto->mock_class_constructor( NETWORK_PKG() . '::new', @_ );
}
sub mock_implementation {
    my $proto = shift;
    $proto->mock_class_constructor( IMPLEMENTATION_PKG() . '::new', @_ );
}

These are for mocking the layers of the library I'm testing (it's a specialized web service API.) In the class that tests the public API layer, I have:

sub startup : Test(startup => 1) {
    my $self = shift;
    $self->SUPER::startup();
    my $test_class = $self->{test_class} = $TEST_CLASS;
    require_ok( $test_class) or $self->BAILOUT("Can't test class we can't require");
    my $impl_mocker = $self->{impl_mocker} = Test::MockObject->new();
    $impl_mocker->mock( ... );
    # etc etc
}

And later...

sub test_03a_list_things : Tests {
    my $self = shift;
    my $cleanup_token = $self->mock_implementation( $self->{impl_mocker} );
    $self->_list_things(@_);
}
sub test_03b_list_things_integration : Tests {
    my $self = shift;
    my $net_mocker = $self->gen_net_mocker();
    my $cleanup_token = $self->mock_network( $net_mocker );
    $self->_list_things( $net_mocker, @_ );
}

The actual test code is in the _list_things() method. I'm just using lexical scoping here, but I get to move all the code that does the mocking to one place, and yet still have a lexical scope. (If I had used local, I would have had to repeat the actual mocking code every time I want to do this. Bad DRY!)

Most of what I've done looks like this, it appears. But hey! Think of what I could do! I think I'm remembering now my initial motivation: it was DRY, per the example above.

Object::Destroyer seems similar

dpisoni on 2006-10-29T04:51:45

... not quite the same, but similar motivation. Rather than just "entangling" the scoping token with the thing they want to control scope of however, it wraps it.